Vuex
1.官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
2.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
3.Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
状态管理
1.可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
2.我们自己也可以封装这样的对象来管理,比如:
Vue.prototype.shareObj = shareObj
Vue.component('cpn1', {
this.shareObj.name
})
Vue.component('cpn2', {
})
const app = new Vue({
el: '.app',
})
但是这样实现不了响应式,需要手动修改,而Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。
管理什么状态
大型开放,会有多个状态,在多个界面间的共享问题。
比如用户的登录状态、用户名称、头像、地理位置信息等等。
比如商品的收藏、购物车中的物品等等。
这些状态信息,可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
单界面的状态管理
单个组件中进行状态管理是一件非常简单的事情
State:就是我们的状态。(可以当做就是data中的属性)
View:视图层,可以针对State的变化,显示不同的信息。
Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。
<template>
<div id="app">
<!-- <h2>{{message}}</h2> -->
<h2>counter:{{counter}}</h2>
<button @click = "counter++">counter +1</button>
<button @click = "counter--">counter -1</button>
</div>
</template>
<script>
export default {
components: { HelloVuex },
name: 'App',
data() {
return{
message:'你好',
counter:0
}
},
}
</script>
<style>
</style>
在这个案例中,有木有状态需要管理呢?没错,就是个数counter。
1.counter需要某种方式被记录下来,也就是State。
2.counter目前的值需要被显示在界面中,也就是View。
3.界面发生某些操作时(我们这里是用户的点击,也可以是用户的input),需要去更新状态,也就是Actions.
多界面状态管理
上面 Vue已经帮我们做好了单个界面的状态管理
1.但是多个视图如果都依赖同一个状态(一个状态改了,多个界面需要进行更新)
2.不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)
3.也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个视图,但是也有一些状态(状态a/状态b/状态c)属于多个视图共同想要维护的
4.状态1/状态2/状态3你放在自己的房间中,自己管理自己用,但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理。
5.Vuex就是为我们提供这个大管家的工具。
全局单例模式
我们现在要做的就是将共享的状态抽取出来,交给Vuex,统一进行管理。
之后,每个视图,按照我规定好的规定,进行访问和修改等操作。
这就是Vuex背后的基本思想。
Vuex状态管理图例
简单的案例
还是实现之前案例 用Vuex
npm install --save vuex
需要在某个地方存放我们的Vuex代码:
先创建一个文件夹store,
并且在其中创建一个index.js文件
在index.js文件中写入如下代码:
index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 1.安装插件
Vue.use(Vuex)
// 2.创建对象
const store = new Vuex.Store({
state: {
counter: 100
},
mutations: {
increment(state) {
state.counter++
},
decrement(state) {
state.counter--
}
},
actions: {
},
getters: {
},
modules: {
}
})
export default store
其次,我们让所有的Vue组件都可以使用这个store对象
来到main.js文件,导入store对象,并且放在new Vue中
这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了
import Vue from 'vue'
import App from './App'
import router from './router'
//引入
import store from './store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
//挂载
store,
render: h => h(App)
})
在App.vue中使用
<template>
<div id="app">
<!-- <h2>{{message}}</h2> -->
<!--注意这里的写法 -->
<h2>{{$store.state.counter}}</h2>
<button @click = "jia">counter +1</button>
<button @click = "jian">counter -1</button>
<hello-vuex></hello-vuex>
</div>
</template>
<script>
import HelloVuex from './components/HelloVuex.vue'
export default {
name: 'App',
data() {
return{
message:'你好',
}
},
components:{
HelloVuex
},
methods:{
jia() {
//注意这里的写法 this.$store.commit
this.$store.commit('increment')
},
jian() {
this.$store.commit('decrement')
}
}
}
</script>
<style>
</style>
HelloVuex.vue
<template>
<div>
<h2>HelloVuex组件</h2>
<!--注意这里的写法 -->
<h2>{{$store.state.counter}}</h2>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
总结:
1.提取出一个公共的store对象,用于保存在多个组件中共享的状态
2.将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
3.在其他组件中使用store对象中保存的状态即可
通过this.$store.state.属性的方式来访问状态
<h2>{{$store.state.counter}}</h2>
通过this.$store.commit('mutation中方法')来修改状态
methods:{
add() {
this.$store.commit('increment')
},
jian() {
this.$store.commit('decrement')
}
}
注意:
我们通过提交mutation的方式,而非直接改变store.state.count。
这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值。
Vuex核心概念
Vuex有几个比较核心的概念:
State
Getters
Mutation
Action
Module
下面进行一一介绍.
State单一状态树
Vuex提出使用单一状态树, 英文名称是Single Source of Truth,也可以翻译成单一数据源。
生活中的例子做一个简单的类比
在国内我们有很多的信息需要被记录,
比如上学时的个人档案,
工作后的社保记录,
公积金记录,
结婚后的婚姻信息,
以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。
这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),
你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。
这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作
(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。
这个和我们在应用开发中比较类似:
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
所以Vuex也使用了单一状态树来管理应用层级的全部状态
单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
一句话:
只创建一个store对象 ,方便查找维护
const store = new Vuex.Store({})
Getters基本使用
什么是getters?
vuex中的getters用于对state中存储的数据进行过滤操作,可以认为,getters是store的计算属性。
对数据进行操作 我们可以使用computed计算属性,比如:
我们对state下的学生对象进行 操作
state: {
counter: 100,
students: [{
id: 1,
name: 'x',
age: 27
}, {
id: 2,
name: 'xi',
age: 10
}, {
id: 3,
name: 'xia',
age: 5
}, {
id: 4,
name: 'xiao',
age: 50
}]
},
在App.vue中使用computed:
//返回age大于20的学生对象
more20stu() {
return this.$store.state.students.filter(s => s.age >20)
}
这样也可以实现,但是如果在另一个组件中也想拿到age大于20的学生对象,就需要在另个组件中也使用computed属性,所以我们可以使用getters属性进行过滤
getters: {
more20stu(state) {
return state.students.filter(s => s.age > 20)
},
这样在组件中使用就可以
//注意写法
<h2>年龄大于20的学生:{{$store.getters.more20stu}}</h2>
如果我们已经有了一个获取所有年龄大于20岁学生列表的getters, 那么代码可以这样来写
more20stu(state) {
//ES6语法 箭头函数
return state.students.filter(s => s.age > 20)
},
//获取age大于20得student得个数
getMore20StuCount(state, getters) {
return getters.more20stu.length
},
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.
比如我们希望根据ID获取student的信息:
getId(state) {
return function(id) {
return state.students.filter(s => s.id === id)
}
}
可以使用ES6语法简写
getId(state) {
return id => {
return state.students.filter(s => s.id === id)
}
}
总结:
1.getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数.
2.getMore20StuCount(state, getters)第二个参数只能是getters,并且不能用第三个参数
Mutation
Mutation用于变更Store里面的数据
1.在vue中只能通过mutation变更Store数据,不可以直接操作Store中的数据。
2.通过这种方式操作虽然操作起来稍微繁琐一点,但是可以击中监控所有数据的变化。
状态更新
Vuex的store状态的更新唯一方式:提交Mutation
Mutation主要包括两部分:
字符串的事件类型(type)
一个回调函数(handler),该回调函数的第一个参数就是state。
mutation的定义方式
//type:increment
//回调函数:state
increment(state) {
state.counter++
},
通过mutation更新
<button @click = "add">counter +1</button>
add() {
this.$store.commit('increment')
},
传递参数
在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数
参数被称为是mutation的载荷(Payload)
App.vue
<button @click = "addCount(5)">counter + 5</button>
methods:{
addCount(count) {
this.$store.commit('increment5',count)
}
}
index.js
mutations: {
increment5(state, count) {
state.counter += count
}
}
但是如果参数不是一个呢?
比如我们有很多参数需要传递.,这个时候, 我们通常会以对象的形式传递, 也就是payload是一个对象.这个时候可以再从对象中取出相关的信息.
App.vue
<button @click = "addStu">添加学生信息</button>
methods:{
addStu() {
let stu = {id:5,name:'xiaoxiao',age:100}
this.$store.commit('incrementStu',stu)
}
}
index.js
mutations: {
incrementStu(state, stu) {
state.students.push(stu)
}
}
提交风格
上面的通过commit进行提交是一种普通的方式
Vue还提供了另外一种风格, 它是一个包含type属性的对象
this.$store.commit({
type:'increment5',
//count:count
//可以使用ES6语法,直接写count
count
})
Mutation中的处理方式是将整个commit的对象作为payload使用, 所以代码如下:
//这里不要直接传count 因为现在count不再是数字,而是一个对象
increment5(state, payload) {
state.counter += payload.count
},
对比
this.$store.commit('increment5',count)
increment5(state, count) {
console.log(count) //5
state.counter += count
},
this.$store.commit({
type:'increment5',
count:count
})
increment5(state, count) {
console.log(count) //{type: "increment5", count: 5}
state.counter += count
},
所以要使用payload
响应规则
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新.
这就要求我们必须遵守一些Vuex对应的规则:
1、提前在store中初始化好所需的属性.
2、当给state中的对象添加新属性时, 使用下面的方式:
方式一: 用新对象给旧对象重新赋值
在state中定义一个info对象
info: {
name: 'yyy',
age: 40,
height: 1.98
}
在App.vue中引用
<h2>{{$store.state.info}}</h2>
如果我们想要修改name属性:
//App.vue
<button @click="updateClick">修改信息</button>
methods:{
updateClick() {
this.$store.commit('UpdateInfo')
}
}
//index.js
UpdateInfo(state) {
state.info.name = 'qqq'
//也可以重新赋值一个对象
state.info = {...state.info, "height": "2.33" }
}
点击按钮即可修改
这两种方法都可以让state中的属性是响应式的
方式二: 使用Vue.set(obj, ‘newProp’, 123)
在之前版本中,无法动态添加和删除属性,比如这样:
UpdateInfo(state) {
state.info.name = 'qqq'
state.info['address'] = '北京'
}
旧版本中这样添加 在devtools中可以显示 但在页面中无法显示(不是响应式的)
所以需要Vue.set(obj, ‘newProp’, 123)
Vue.set(state.info, "address", "北京")
也可以进行删除某个属性:
Vue.delete(state.info, "age")
不过在新版本中可以直接添加属性而且是响应式的
state.info['address'] = '北京'
也可以直接删除属性而且是响应式的
delete state.info.age
常量类型
在mutation中, 定义了很多事件类型(也就是其中的方法名称).
当项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多.
方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
所以需要常量替代Mutation事件的类型.
我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.
创建一个文件: mutation-types.js, 并且在其中定义我们的常量.
src/store/mutation-types.js
export const INCREMENT = 'increment'
App.vue
导入:
import {INCREMENT} from './store/mutatios-types'
使用:
methods:{
add() {
this.$store.commit(INCREMENT)
}
}
index.js
import * as myMutations from './mutatios-types'
mutations: {
[myMutations.INCREMENT](state) {
state.counter++
}
}
同步函数
Vuex要求我们Mutation中的方法必须是同步方法.
主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.
mutations: {
UpdateInfo(state) {
setTimeout(() => {
state.info.name = 'qqq'
}, 2000)
}
},
此时在网页中2s后 name会被修改 但是devtools中不会修改
所以不能在mutations中进行异步操作
Action
我们强调, 不要再Mutation中进行异步操作.
但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的. 这个时候怎么处理呢?
Action类似于Mutation, 但是是用来代替Mutation进行异步操作的.
Action的基本使用代码如下:
mutations: {
UpdateInfo(state) {
state.info.name = 'qqq'
}
},
actions: {
aupdateInfo(context) {
setTimeout(() => {
context.commit('UpdateInfo')
}, 2000)
}
},
context是和store对象具有相同方法和属性的对象.
也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.
但是注意, 这里它们并不是同一个对象.
Action的分发
在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch
App.vue
methods:{
updateClick() {
this.$store.dispatch('aupdateInfo')
}
},
同样的, 也是支持传递payload
updateClick() {
this.$store.dispatch('aupdateInfo','我是payload')
}
aupdateInfo(context, payload) {
setTimeout(() => {
context.commit('UpdateInfo')
console.log(payload); //我是payload
}, 2000)
}
action返回的Promise
如果我们想在修改后返回信息:
1.通过回调函数
updateClick() {
this.$store.dispatch('aupdateInfo',{
message:"携带的某些信息",
success:() =>{
console.log('修改成功');
}
})
}
actions: {
aupdateInfo(context, payload) {
setTimeout(() => {
context.commit('UpdateInfo')
console.log(payload.message); //携带的某些信息
payload.success() //修改成功
}, 2000)
}
},
2.通过promise
actions: {
aupdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('UpdateInfo')
resolve('要传递得信息')
}, 2000)
})
}
}
updateClick() {
this.$store.dispatch('aupdateInfo')
.then(
data => {
console.log('完成了修改');
//可以传递过来信息
console.log(data); //要传递得信息
})
}
Module
1、Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
2、当应用变得非常复杂时,store对象就有可能变得相当臃肿.
3、为了解决这个问题, Vuex允许我们将store分割成模块(Module), 而每个模块拥有自己的state、mutation、action、getters等
Module中的state
const moduleA = {
state: {
name: 'kobe'
}
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
<h2>{{$store.state.a.name}}</h2>
Module中的mutations
const moduleA = {
state: {
name: 'kobe'
},
mutations: {
UpdateName(state, payload) {
state.name = payload
}
},
}
<button @click="UpdateNameClick">修改moduleA的name</button>
UpdateNameClick() {
this.$store.commit('UpdateName','Curry')
},
Module中的getters
const moduleA = {
state: {
name: 'kobe'
},
mutations: {
UpdateName(state, payload) {
state.name = payload
}
},
getters: {
GetFullName(state) {
return state.name + '111'
},
GetFullName2(state, getters) {
return getters.GetFullName + '222'
},
GetFullName3(state, getters, rootState) {
return getters.GetFullName + '222' + rootState.counter
}
},
}
const store = new Vuex.Store({
state: {
//rootState.counter就是这的state
counter: 100,
}
})
<h2>{{$store.getters.GetFullName}}</h2>
<h2>{{$store.getters.GetFullName2}}</h2>
<h2>{{$store.getters.GetFullName3}}</h2>
Module中的actions
const moduleA = {
state: {
name: 'kobe'
},
mutations: {
UpdateName(state, payload) {
state.name = payload
}
},
getters: {
GetFullName(state) {
return state.name + '111'
},
GetFullName2(state, getters) {
return getters.GetFullName + '222'
},
GetFullName3(state, getters, rootState) {
return getters.GetFullName + '222' + rootState.counter
}
},
actions: {
aUpdateName(context) {
setTimeout(() => {
context.commit('UpdateName', 'James')
})
}
},
modules: {}
}
<button @click="ayncUpdateNameClick">异步修改moduleA的name</button>
ayncUpdateNameClick() {
this.$store.dispatch('aUpdateName')
}
Module局部状态
mutation和getters接收的参数是局部状态对象
actions接收一个context参数对象
局部状态通过 context.state 暴露出来
根节点状态则为 context.rootState
项目结构
当我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰.
1、把mutations getters actions单独抽离成一个文件,并引入到index.js中
2、建立modules文件夹,将各个module.js放入,并导入到index.js
3、state不用单独抽离成文件,方便直观观察到
最后目录结构如下:
完