vuex (6)
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时, store对象就有可能变得相当臃肿。
为了解决以上问题,Vuex允许我们将store分割成模块( module )。 每个模块拥有自己的 state、mutation、 action、 getter 甚至是嵌套子模块——从上至下进行同样方式的分割。
const moduleA = { state: () => ({...}), mutations: {...}, actions: {}, getters: {} } const moduleB = { state: () => ({...}), mutations: {...}, actions: {}, getters: {} } const store = createStore({ modules:{ a:moduleA, b:moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态
模块的局部状态
对于模块内部的mutation 和 getter, 接收的第一个参数是模块的局部状态对象。
const moduleA = { state: () => ({ count: 0 }), mutations: { increment (state){ // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count *2 } } }
同样, 对于模块内部的action, 局部状态通过 context.state 暴露出来, 根节点状态则为 context.rootState :
const moduleA = { // ... actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if((state.count + rootState.count) % 2 ===1){ commit("increment") } } } }
对于模块内部的 getter ,根节点状态会作为第三个参数暴露出来
const moduleA = { // ... getters:{ sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } } }
默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的,这样使得多个模块能够对同一个 action 或 mutation 作出响应。 Getter 同样也默认在全局命名空间,但是目前这并非出于功能上的目的(仅仅是维持现状来避免非兼容性变更)。必须注意,不要再不同的、无命名空间的模块中定义两个相同的 getter 从而导致错误。
如果希望你的模块具有更高的封装性和复用性,你可以通过添加 namespace:true 的方式 使其成为带命名空间的模块。当模块被注册后,它的所有 getter、 action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const store = createStore({ modules: { account: { namespace:true, // 模块内容 (module assets) state: () => ({...}), //模块的状态已经是嵌套的了,使用 `namespace` 属性不会对其产生影响 getters: { isAdmin () {...} // ->getters['account/isAdmin'] }, actions: { login () {...} // -> dispatch('account/login') }, mutations: { login () {...} // ->commit('account/login') }, modules: { myPage: { state: () => ({...}), getters: { profile (){...} // ->getters['account/profile'] } }, // 进一步嵌套命名空间 posts: { namespace: true, state: () => ({...}), getters: { popular () {...} // ->getters['account/posts/popular'] } } } } } })
启用了命名空间的 getter 和 action 会收到局部化的 getter , dispatch 和 commit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。
在带命名空间的模块内访问全局内容 (Global Assets)
如果你希望使用全局 state 和 getter, rootState 和 rootGetters 会作为第三和第四参数传入getter, 也会通过context 对象的属性传入 action。
若需要在全局命名空间内分发 action 或 提交 mutation,将 {root: true} 作为第三参数传给 dispatch 或 commit 即可。
modules: { foo: { namespace:true, getters: { // 在这个模块的getter中, `getters`被局部化了 // 你可以使用 getter 的第四个参数来调用 `rootGetters` someGetter (state, getters, rootState, rootGetters){
getters.someOtherGetter // ->`foo/someOtherGetter` rootGetters.someOtherGetter // ->someOtherGetter } }, actions: { // 在这个模块中,dispatch 和 commit也被局部化了 // 你可以使用 getter 的第四个参数来调用 `rootGetters` 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){...} } } }
在带命名空间的模块注册全局 action
若需要在带命名空间的模块注册全局 action, 你可以添加 root: true, 并将这个 action 的定义放在函数 handler 中。
{ actions: { someOtherAction ({dispatch}){ dispatch('someAction') } }, modules:{ foo: { namespace: true, actions: { someAction: { root: true, handler (namespaceContext, payload) {...} // ->'someAction' } } } } }
带命名空间的绑定函数
当使用 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() ]) }