Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
Vuex是什么?
Vuex官方解释:
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
有点抽象?可以这么理解:
可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。再将这个对象放在顶层的Vue实例中,让其他组件可以使用。那么多个组件就可以共享这个对象中的所有变量属性了。
可不可以自己封装一个对象实现相同的功能?
当然可以,但是Vue的一大核心是响应式,即Vuex是经过响应式处理的,自己封装的话要处理响应式,显得非常复杂,所以直接使用Vuex插件比较方便。
项目中哪些场景会用到Vuex?
用户登陆信息、商品收藏和购物车商品信息等,这些信息都是要在多个页面即多个组件之间进行共享的,如果用一般的组件通信方式传递信息就会显得非常繁杂,这时就可以用Vuex将这些多组件共享的信息抽取出来统一管理。
核心思想:把组件的共享状态抽取出来,以一个全局单例模式管理
Vuex的五大核心属性
- state 单一状态树,存放状态信息的地方
- getters 加工state成员,类似于计算属性
- mutations 状态更新的唯一方式
- actions 代替 mutations 进行异步操作
- modules 模块化状态管理
下面详细介绍各个属性的作用:
一、state
const store = new Vuex.Store({
state:{
name:'CC' //需要统一管理的状态信息
}
})
可以在将共享信息存放在 state 里面进行统一管理,然后在组件中通过this.$store.state.name
就能取到里面的信息。
二、getters
const store = new Vuex.Store({
state:{
name:'CC' //需要统一管理的状态信息
}
getters:{
nameInfo(state){
return "姓名:"+state.name //返回格式 姓名:CC
}
})
类似于计算属性,对 state 里的信息进行加工再提供给组件,组件中使用this.$store.getters.nameInfo
Getters中的方法有两个默认参数:
state 当前VueX对象中的状态对象
getters 当前getters对象,用于将getters下的其他getter拿来用
例如:
getters:{
nameInfo(state){
return "姓名:"+state.name
},
fullInfo(state,getters){
return getters.nameInfo+'年龄:'+state.age
} //返回格式 姓名:CC 年龄:state.age
}
三、mutations
const store = new Vuex.Store({
state:{
name:'CC' //需要统一管理的状态信息
}
getters:{
nameInfo(state){
return "姓名:"+state.name //返回格式 姓名:CC
}
mutations:{
edit(state){
state.name = 'jack' //更新状态信息
}
}
})
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,组件中使用方式:
this.$store.commit('edit') //不用传参时
//需要传参时用下面方式,这里的 payload 称为载荷
this.$store.commit('edit',{age:15,sex:'男'})
//或
this.$store.commit({
type:'edit',
payload:{
age:15,
sex:'男'
}
})
Mutations 需遵守 Vue 的响应规则
既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:
(1)最好提前在你的 store 中初始化好所有所需属性。
(2)当需要在对象上添加新属性时,应该:
添加:例如对state对象中添加一个age成员 Vue.set(state,"age",15)
;
或者以新对象替换老对象的方式。state = { ...state, age: 15 }
删除:将刚刚添加的age成员删除:Vue.delete(state,'age')
Mutations 必须是同步函数
在 Mutations 进行异步操作的话,Vue开发工具VueDevtools追踪不到状态的改变,就无法进行准确的debug。
四、actions
const store = new Vuex.Store({
state,
getters,
mutations,
actions:{
aEdit(context,payload){
setTimeout(()=>{
context.commit('edit',payload) //执行Mutations的commit,不能直接修改状态
},2000)
}
}
})
代替 mutations 进行异步操作(Action 提交的是 mutation,而不是直接变更状态),也可以将一些复杂的更新方法放在里面,组件中使用方式:this.$store.dispatch('aEdit',{age:15})
注:Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。context 对象不是 store 实例本身,之后介绍到 Modules会说明。
五、modules
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
使用 modules 需要注意的几个细节:
(1)对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态,即moduleA的状态
state.count++
}
}
}
(2)同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:(可以看出context 对象不是 store 实例本身)
const moduleA = {
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
(3)对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命名空间:
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const moduleA = {
namespaced: true
state: { ... }, //不影响
mutations: { login () { ... } }, //名字变为a/login,则调用时commit('a/login')
actions: { ... },
getters: { ... }
}
重点原则:如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
模块化+命名空间之后, 数据都是相对独立的, 如何在模块 A 调用 模块 B 的state, actions, mutations, getters(腾讯面试题)
(关于这块内容不太好理解,请参考官方文档以及推荐一篇博客:大型Vuex项目 ,使用module后, 如何调用其他模块的 属性值和方法?)
项目中具体使用细节
安装Vuex插件
npm i vuex -s
项目目录规范:
store:.
│ actions.js
│ getters.js
│ index.js
│ mutations.js
即把每个属性单独抽取成一个js文件,index.js文件中只放以下内容:
import Vue from 'vue'
import Vuex from 'vuex' //es6模块化指令,导入插件
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex) //使用插件
const state = {
cartList: []
}
const store = new Vuex.Store({
state,
mutations,
actions,
getters
})
mutations.js文件举例:
const mutations = {
addCart(state, info) { }
}
export default mutations //es6模块化指令
在Vue全局实例中添加store对象
new Vue({
render: h => h(App),
store,
}).$mount('#app')
Vuex工作流程图:
(官方Vuex流程图,非常重要,一定要掌握,有利于理解和记忆Vuex的使用,一直觉得组件与Mutions之间要多画一条线表示可以直接commit,而不用总是经过actions)
什么情况下应该使用 Vuex?
官方建议:
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
参考文献:
Vuex官方文档
VueX(Vue状态管理模式)