5.modules
背景:
在Vuex中所有的状态都放在state里面,如果项目比较复杂,那state是一个很大的对象,store对象也将对变得非常大,难于管理。
modules:可以让每一个模块拥有自己的state、mutations、action、getters,使得结构非常清晰,方便管理,甚至是嵌套子模块——从上至下进行同样方式的分割。
尤其在多人开发同一个项目的时候,自己负责自己相关的模块的state,会让操作state都会变得更加直观。
先来看下示例:
const moduleA = {
state: {
name:'lily',
role:'超级管理员',
sex:'女'
},
getters: {
myProfile(state, getters, rootState, rootGetters) {
// 这里的 `state` 对象是模块的局部状态
console.log(rootState,getters,rootState,rootGetters,'lily')
return state.name + state.role
}
},
mutations: {
SET_ROLE(state,str) {
// 这里的 `state` 对象是模块的局部状态
state.role = str;
}
},
actions: {
SET_ROLE (context) {
//在 action 中,由于它接收过来的数据都被包在context对象中的,所以解出来没有什么顺序的限制,你可以这样:
let { state,commit,rootState, } = context;
//也可以这样:
//let { commit,state,rootGetters,rootState} = context;
commit('SET_ROLE','普通管理员');
console.log(context)
}
},
}
const moduleB = {
state: {
token:'kfep0f91265jiefj2512',
permission:[]
},
getters: {
getToken({state, getters, rootState}) {
//console.log(state, getters, rootState)
return state.token
}
},
mutations: { },
actions: { }
}
const store = new Vuex.Store({
state: {
todoLists: [1,5,5,2,3,6,6,5,0,8],
},
mutations:{
// 新增list
ADDLIST(state, item) {
state.todoLists.push(item);
console.log(state.todoLists,'state.todoLists')
},
},
actions:{
addList(context, item) {
context.commit('ADDLIST', item);
},
},
modules: {
a: moduleA,
b: moduleB
}
})
new Vue({
el: '#app',
data: {
name: 'init name'
},
store: store,
mounted: function() {
//console.log(this,myStore.getters.todoCount);
},
computed:{
ROLE:function() {
this.$store.dispatch('SET_ROLE');
},
...Vuex.mapGetters(['myProfile'])
}
})
store.state.a // -> moduleA 的state状态
store.state.b // -> moduleB 的state状态
1. 模块的局部状态
我们从上面这个示例中作如下分析:
1.moduleA中的mutation和getter接受到的第一个参数state是moduleA的局部状态对象,只属于模块本身所有,并且对于moduleA中的getter,根节点状态会作为第三个参数暴露出来,这个参数可以让我们访问 store 根节点的数据 state
const moduleA = {
state: {
name:'lily',
role:'超级管理员',
sex:'女'
},
getters: {
myProfile(state, getters, rootState) {
// 这里的 `state` 对象是模块的局部状态
console.log(rootState,getters,rootState,'lily')
return state.name + state.role
}
},
mutations: {
SET_ROLE(state,str) {
// 这里的 `state` 对象是模块的局部状态
state.role = str;
}
}
}
- moduleA中的action局部状态通过context.state暴露出来,根节点状态则为context.rootState
const moduleA = {
state: {
name:'lily',
role:'超级管理员',
sex:'女'
},
actions: {
SET_ROLE (context) {
let { state,commit,rootState, } = context;
commit('SET_ROLE','普通管理员');
console.log(context)
}
},
}
2.命名空间
前面我们已经知道了,模块内部的action、mutation和getter默认是注册在全局命名空间的,它有以下两个弊端:
- 不同模块中有相同命名的mutations、actions时,不同模块对同一 mutation 或 action 会作出响应。
- 当一个项目中store分了很多模块的时候,在使用辅助函数mapState、mapGetters、mapMutations、mapActions时,很难查询,引用的state、getters、mutations、actions来自于哪个模块,不便于后期维护。
如果我们只想让他们在当前的模块中生效,应该怎么办呢?通过添加namespaced:true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
我们把上面的代码添加namespaced:true来看下
const moduleA = {
namespaced:true,
state: {
name:'lily',
role:'超级管理员',
sex:'女'
},
actions: {
SET_ROLE (context) {
let { state,commit,rootState, } = context;
commit('SET_ROLE','普通管理员');
console.log(context)
}
},
}
以上代码会报下面这样的错误:
vuex.js:423 [vuex] unknown action type: SET_ROLE
vuex.js:855 [vuex] unknown getter: myProfile
这是因为在全局actions已经找不到这个SET_ROLE这个事件类型了,因为他的路径变了,不再属于全局了,他仅仅属于moduleA了。所以我们需要修改成这样:
computed:{
ROLE:function() {
this.$store.dispatch('a/SET_ROLE');
},
//这里的a是moduleA的模块命名空间名字
...Vuex.mapGetters('a',[myProfile'])
}
这里需要注意一点的就是,如果一个模块启用了命名空间,那么启用了命名空间的 getter 和 action 会收到局部化的getter,dispatch和commit。换言之,你在使用模块内容(moduleassets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。
2.1 那么我们如何在带命名空间的模块内访问全局内容呢?
通过前面的学习,我们要重点划线标记下:
++如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。++
如果我想要在带命名空间的模块内dispatch actions 或者 commit mutation,那么将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。来看下面的示例:
const moduleA = {
namespaced:true,
state: {
name:'lily',
role:'超级管理员',
sex:'女'
},
actions: {
SET_ROLE (context) {
let { state,commit,rootState, } = context;
commit('SET_ROLE','普通管理员');
//commit('ADDLIST','huanle',{root:true})
dispatch('addList','lily',{root:true})
console.log(context)
}
},
}
通过上面的代码,todoLists里面多了个lily这项,说明我们从模块内部分发全局的actions已经成功。
2.2如何在带命名空间的模块内注册全局 action
若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。
const moduleA = {
namespaced:true,
state: {
name:'lily',
role:'超级管理员',
sex:'女'
},
actions: {
//SET_ROLE (context) {
//let { state,commit,rootState, } = context;
//commit('SET_ROLE','普通管理员');
//commit('ADDLIST','huanle',{root:true})
//dispatch('addList','lily',{root:true})
//console.log(context)
//},
//若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中
SET_ROLE: {
root:true,
handler (namespacedContext, payload) {
let { state,commit,rootState,dispatch } = namespacedContext;
commit('SET_ROLE','普通管理员');
commit('ADDLIST','huanle',{root:true})
//dispatch('addList','lily',{root:true})
console.log(state,rootState,'在带命名空间的模块注册全局 action已经成功')
}
}
},
}
简单解释下,这里的 namespacedContext 就相当于当前模块的上下文对象,payload 是调用的时候所传入的参数,当然也叫载荷。
示例就讲到这里吧,接下来继续学习下一要点。
3.带命名空间的绑定函数
当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:先看下这里的示例,这是第一种方式:
computed:{
ROLE:function() {
this.$store.dispatch('a/SET_ROLE');
},
//这里的a是moduleA的模块命名空间名字
...Vuex.mapGetters({
myProfile:'a/myProfile'
})
}
对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
computed:{
ROLE:function() {
this.$store.dispatch('a/SET_ROLE');
},
//这里的a是moduleA的模块命名空间名字
...Vuex.mapGetters('a',[myProfile'])
}
第二种方式:
你可以通过使用createNamespacedHelpers创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:
这里我就直接引用官方的例子了:
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
4.模块动态注册
哈哈。这部分内容各位看官就去官方文档翻翻看吧,因为博主觉得动态模块一般在业务项目中用得特别少,几乎用不到,所以只学项目中需要用到的,但有可能会在第三方库中,类似封装好的一些框架中用到这些,所以大家有需求需要动态添加模块时,去看下官方文档就好,因此这里也不赘述了。谢谢大家。
写在最后
至此,Vuex 的核心概念就已经全部讲完了。虽然这套框架有点难,但其实只要我们花点心思在上面,也是能弄懂的。大家一定要在项目中多运用,多实操。