上面我们介绍如何拆分项目,采用的是按属性的方式去拆分,将getters/actions/mutations等属性拆分到不同的文件中,以及介绍了常量代替XXX事件类型;接下来,我们介绍一下按另外的一个维度去拆分我们的store,‘按功能’,按功能拆分的话,就是我们的标题Module(模块)。
-
我们先来看下官方文档是怎么介绍Module的:
- 看了图中的描述,你或许已经区分了这里使用的按功能拆分Module和我们上次介绍的按属性拆分的异同了;就像图中的场景一样,我们有一个总store,在这里面根据不同的功能,我们加了两个不同的store,每个store里面维护自己的state,以及自己的actions/mutations/getters。
不说废话,我们用代码实现一下
-
- 我们在之前的store上,增加一个子模块,名字叫mutou,主要代码如下:
- 目录结构如下
store |___index.js |___state.js |___mutations.js |___getters.js |___actions.js |___mutation-types.js |___getter-types.js |___action-types.js |___mutou <---新增mutou文件夹 |___index.js <---新增index.js文件,新增的模块我们暂时先不按属性进行拆分了,其实是完全可以再按属性进行拆分,属性拆分和功能拆分是完全可以合在一起的
- index.js文件如下:
import mutou2 from './mutou2/index' const mutou = { state: { name:'i am mutou', }, mutations: { changeName(state,payload){ console.log(payload.name); } }, getters:{}, } export default mutou
- store/index.js文件修改如下,引入我们新创建的mutou模块:
import Vue from 'vue' import Vuex from 'vuex' import state from './state' import getters from './getters' import mutations from './mutations' import actions from './actions' import mutou from './mutou/index' <--- 引入mutou模块 Vue.use(Vuex) const store = new Vuex.Store({ modules:{mutou}, <--- 将木头模块装载上 state, getters, mutations, actions, }) export default store
- 访问state -- 我们在App.vue测试访问mutou模块中的state中的name,结果如下:
console.log(this.$store.state.mutou.name);
- 访问mutation -- 我们在App.vue测试提交mutou模块中的mutation中的changeName,结果如下:
this.$store.commit('changeName',{where:'我是从App.vue进来的'})
-
1. 模块的局部状态
我们通过下面的代码可以了解到在不同的属性里是怎么访问模块内的状态或者根状态:mutations: { changeName(state,payload){ // state 局部状态 console.log(state) console.log(payload.where); } }, getters:{ testGetters (state, getters, rootState) { // state 局部状态 console.log(state) // 局部 getters, console.log(getters) // rootState 根节点状态 console.log(rootState) } }, actions: { increment ({ state, commit, rootState }) { // state 局部状态 console.log(state) // rootState 根节点状态 console.log(rootState) } }
-
2. 命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。如果希望你的模块具有更高的**封装度**和**复用性**,你可以通过添加 ```namespaced: true``` 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const mutou = { namespaced:true, <--- 增加namespaced属性,值为true state: { name:'i am mutou', }, ......
此时,再次commit我们刚才的changeName的mutation,浏览器会报找不到mutation的错误:
this.$store.commit('changeName',{where:'我是从App.vue进来的'})
此时,我们修改为下面的模式,加上命名空间的路径即可恢复正常。
this.$store.commit('mutou/changeName',{where:'我是从App.vue进来的'})
其余的getters、actions同mutation一样,具体使用可以参考vuex的官方文档官方文档的Module在这里。
-
3. 在带命名空间的模块内访问全局内容
如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
-
比较简单,直接贴官方示例代码:
modules: { foo: { namespaced: true, getters: { // 在这个模块的 getter 中,`getters` 被局部化了 // 你可以使用 getter 的第四个参数来调用 `rootGetters` someGetter (state, getters, rootState, rootGetters) { getters.someOtherGetter // -> 'foo/someOtherGetter' rootGetters.someOtherGetter // -> 'someOtherGetter' }, someOtherGetter: state => { ... } }, actions: { // 在这个模块中, dispatch 和 commit 也被局部化了 // 他们可以接受 `root` 属性以访问根 dispatch 或 commit someAction ({ dispatch, commit, getters, rootGetters }) { getters.someGetter // -> 'foo/someGetter' rootGetters.someGetter // -> 'someGetter' dispatch('someOtherAction') // -> 'foo/someOtherAction' dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction' commit('someMutation') // -> 'foo/someMutation' commit('someMutation', null, { root: true }) // -> 'someMutation' }, someOtherAction (ctx, payload) { ... } } } }
-
4. 在带命名空间的模块注册全局 action
- 若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。
- 我们来实践一下,在mutou/index.js的action里面加上:
globalAction: { root: true, handler (namespacedContext, payload) { console.log('i am in global action...'); } // -> 'someAction' }
- 在App.vue里运行,注意,我没有加mutou/XXX,没有加命名空间的路径,如果能正常访问,即代表该action的确注册到了全局上。
this.$store.dispatch('globalAction')
-
5.带命名空间的绑定函数
- 当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
computed: { ...mapState({ a: state => state.some.nested.module.a, b: state => state.some.nested.module.b }) }, methods: { ...mapActions([ 'some/nested/module/foo', // -> this['some/nested/module/foo']() 'some/nested/module/bar' // -> this['some/nested/module/bar']() ]) }
- 对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
computed: { ...mapState('some/nested/module', { a: state => state.a, b: state => state.b }) }, methods: { ...mapActions('some/nested/module', [ 'foo', // -> this.foo() 'bar' // -> this.bar() ]) }
- 而且,你可以通过使用 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' ]) } }
- 当使用 mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
以上是对module的简单介绍,其实这里就是一种思想,分而治之嘛,将复杂的进行拆分,可以更有效的管理,上面介绍的一些方法就是在拆分后,应该注意哪些点,哪些点是因为拆分而需要和原来的不拆分的逻辑做区分的,哪些是可以避免重复代码的操作等等。
其实以上并不是module的全部,还有一些比如‘模块动态注册’、‘模块重用’等方法这里就没介绍,如果你在项目中使用到了,再进行查阅即可,有时候不需要完全理解,知道有这个东西就行,知道出了问题的时候该去哪查资料就够啦。