Module
由于使用单一状态树, 应用的所有状态会集中到一个比较大的对象. 当应用变得非常复杂时, store 对象就有可能变得相当臃肿
为了解决以上问题, Vuex 允许我们将 store 分割成模块. 每个模块拥有自己的 state、Mutation、Action、Getter、甚至是嵌套子模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const moduleA = { state: { ... }, mutaions: { ... }, actions: { ... }, getters: { ... } }
const moduleB = { state: { ... }, mutaions: { ... }, actions: { ... }, getters: { ... } }
const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB, } })
store.state.a store.state.b
|
模块的局部状态
对于模块内部的 mutaion 和 getter, 接受的第一个参数是模块的局部状态对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const moduleA = { state: { count: 0 }, mutaions: { increment(state) { state.count++ } },
getters: { doubleCount(state) { return state.count * 2 } } }
|
同样, 对于模块内部的 action, 局部状态通过 context.state 暴露出来, 根节点状态则为 context.rootState:
1 2 3 4 5 6 7 8 9 10
| const moduleA = { state: { ... }, actions: { incrementIfOddOnRootSum({state, commit, rootState}){ if((state.count + rootState.count) % 2 === 1) { commit('increment') } } } }
|
对于模块内部的 getter, 根节点状态会作为第三个参数暴露出来:
1 2 3 4 5 6 7 8
| const moduleA = { getters: { sumWithRootCount(state, getters, rootState) { return state.count + rootState.count } } }
|
命名空间
默认情况下, 模块内部的 action、mutation 和 getter 是注册在全局命名空间的–这样使得多个模块能够对同一 mutation 或 action 做出反应
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| const store = new Vuex.Store({ modules: { account: { namespaced: true
state: { ... } getters: { isAdmin () { ... } }, actions: { login () { ... } }, mutations: { login () { ... } },
modules: { myPage: { state: { ... }, getters: { profile () { ... } } },
posts: { namespaced: true,
state: { ... }, getters: { popular () { ... } } } } } } })
|
启用了命名空间的 getter 和 action 会收到局部化的 getter, dispatch 和 commit. 换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码
在带命名空间的模块内访问全局内容
如果希望使用全局 state 和 getter, rootState 和 ``rootState会作为第三和第四参数传入 getter, 也会通过context对象的属性传入action`
若需要在全局命名空间内分发 action 或提交 mutation, 将 { root: true } 作为第三参数传给 dispatch 或 commit 即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| modules: { foo: { namespaced: true,
getters: { someGetter( state, getters, rootState, rootGetters) { getters.someOtherGetter rootGetters.someOtherGetter }, someOtherGetter: state => { ... } },
acctions: { someAction({dispatch, commit, getters, rootGetters}) { getters.somegetter rootGetters.someGetter
dispatch('someOtherAction') dispatch('someOtherAction', null, { root: true })
commit('someMutation') commit('someMutation', null, { root: true }) }, someOtherAction(ctx, payload) { ... } } } }
|
在带命名空间的模块注册全局 action
若要在带命名空间的模块注册全局 action, 可以添加 root: true, 并将这个 action 定义放在函数 handler 中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| { actions: { someOtherAction({dispatch}) { dispatch('someAction') } }, modules: { foo: { namespaced: true
actions: { someAction: { root: true, handler( namespacedContext, patload) { ... } } } } } }
|
带命名空间的绑定函数
当使用 mapState、mapgetters、mapActions 和 mapMutations 这些函数来绑定带命名空间的模块时, 写起来可能比较繁琐
1 2 3 4 5 6 7 8 9 10 11 12
| computed: { ...mapState({ a: state => state.some.nested.module.a, b: state => state.some.nested.module.b, }) } methods: { ...mapActions([ 'some/nested/module/foo', 'some/nested/module/bar' )] }
|
对于上述情况, 可以将模块的空间名称字符串作为第一个参数传递给上述函数, 这样所有绑定都会自动将该模块作为上下文
1 2 3 4 5 6 7 8 9 10 11 12 13
| computed: { ...mapState('some/nested/module', { a: state => state.a, b: state => state.b, }) }
methods: { ...mapActions('some/nested/module', { 'foo', 'bar' }) }
|
而且可以使用 createNamespacedHelpers 创建基于某个命名空间辅助函数, 它返回一个对象, 对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { createNamespacedHelpers } from 'vuex'
const { mapState, mapAcrions } = createNamespacedHelpers('some/nested/module')
export default { computed: { ...mapState({ a: state => state.a, b: state => state.b }) }, methods: { ...mapActions(['foo', 'bar']) } }
|
给插件开发者的注意事项
如果你开发的插件(Plugin)提供了模块并允许用户将其添加到 Vuex store,可能需要考虑模块的空间名称问题。对于这种情况,你可以通过插件的参数对象来允许用户指定空间名称:
1 2 3 4 5 6 7 8 9
|
export function createPlugin(options = {}) { return function(store) { const namespace = options.namespace || '' store.dispatch(namespace + 'pluginAction') } }
|
模块动态注册
在 store 创建之后, 可以使用 store.registerModule 方法注册模块:
1 2 3 4 5 6 7 8
| store.registerModule('myModule', { })
store.registerModule(['nested', 'myModule'], { })
|
之后就可以通过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。
模块动态注册功能使得其他 Vue 插件可以通过在 store 中附加新模块的方式来使用 Vuex 管理状态。例如,vuex-router-sync 插件就是通过动态注册模块将 vue-router 和 vuex 结合在一起,实现应用的路由状态管理。
你也可以使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建 store 时声明的模块)
保留 state
在注册一个新 module 时, 你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState 选项将其归档:store.registerModule('a', module, { preserveState: true })。
当你设置 preserveState: true 时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。
模块重用
有时我们可能需要创建一个模块的多个实例,例如:
- 创建多个 store, 他们公用一个模块
- 在一个 store 中多次注册同一模块
如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。
实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):
1 2 3 4 5 6 7 8
| const MyReusableModule = { state() { return { foo: 'bar' } } }
|