5、Modules(模块化)
以往我们所写的store都是一个大整体,从actions到mutations到state到getters这样全都放在一起,如果组件不多还好,但是涉及的组件一多,就会显得结构十分臃肿,不利于管理,所以我们需要使用模块化的store库。
Vuex允许我们将store分割成一个个模块,每个模块拥有自己的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 } }) 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、getter是注册在全局命名空间的,这样就使得多个模块能够对同一个mutation或者action做出响应。
可以通过添加namespace:true 的方式来使其成为带命名空间的模块,当模块注册后,它的所有action、mutation、getter都会自动根据模块注册的路径来调整命名。这样就提高了模块的封装性:
const store = new Vuex.Store({ modules: { account: { namespaced: true, // 模块内容(module assets) state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响 getters: { isAdmin () { ... } // -> getters['account/isAdmin'] }, actions: { login () { ... } // -> dispatch('account/login') }, mutations: { login () { ... } // -> commit('account/login') }, // 嵌套模块 modules: { // 继承父模块的命名空间 myPage: { state: () => ({ ... }), getters: { profile () { ... } // -> getters['account/profile'] } }, // 进一步嵌套命名空间 posts: { namespaced: true, state: () => ({ ... }), getters: { popular () { ... } // -> getters['account/posts/popular'] } } } } } })
启用了命名空间后的getter和action会收到局部化的getter,dispatch和commit。也就是说,在使用模板内容时,不再需要在同一模块内再额外添加空间名前缀了。
在带命名空间的模块内访问全局内容(Global Assets)
如果你希望使用全局 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) { ... } } } }
在带命名空间的模块注册全局action
如果需要在带命名空间的模块注册全局action,可以添加root : true,并将这个action的定义放在函数handler中,例如:
{ actions: { someOtherAction ({dispatch}) { dispatch('someAction') } }, modules: { foo: { namespaced: true, actions: { someAction: { root: true, handler (namespacedContext, 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() ]) }
而且,可以通过使用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' ]) } }
模块动态注册
在创建store之后,就可以使用store.registerModule
方法来注册模块了:
import Vuex from 'vuex' const store = new Vuex.Store({ /* 选项 */ }) // 注册模块 `myModule` store.registerModule('myModule', { // ... }) // 注册嵌套模块 `nested/myModule` store.registerModule(['nested', 'myModule'], { // ... })
然后就能通过store.state.myModule和store.state.nested.myModule访问模块的状态。
保留state
当注册了一个新的module时,如果想保留过去的state,可以通过preserveState选项将其归档:store.registerModule('a',module,{preserveState:true})。
当你设置 preserveState: true
时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。
模块的重用
当我们需要创建一个模块的多个实例,比如:
创建多个store,他们共用同一个模块 (例如当 runInNewContext
选项是 false
或 'once'
时,为了在服务端渲染中避免有状态的单例)
在一个store中多次注册同一个模块
如果我们使用一个纯对象来声明模块的状态,这个状态对象会通过引用而被共享,导致了状态对象被修改时store或模块间数据相互污染的问题。
解决办法是使用一个函数来声明模块状态(仅2.3.0+支持)
const MyReusableModule = { state: () => ({ foo: 'bar' }), // mutation, action 和 getter 等等... }
四种map辅助函数的用法
使用前都需要先进行类似这种形式的引入:improt {mapState} from 'vuex'
mapState
用于帮助映射state中的数据成为计算属性
...mapState({he:'sum',xuexiao:'school',xueke:'subject'}),(对象写法)
...mapState(['sum','school','subject']),(数组写法)
mapGetters
用于帮助映射getters中的数据成为计算属性
...mapGetters({beishu:'bigSum'}),(对象写法)
...mapGetters(['bigSum']),(数组写法)
上面两种写法,其中对象写法的内部的方法名与state(getters)中的属性名可以不同,但数组写法时,两者必须同名
mapActions
用于帮助生成与actions对话的方法,即:包含$store.dispatch('x',x)的函数
mapActions的对象写法,方法会调用dispatch去联系actions
...mapActions({Odd:'odd',waitjia:'wait'}),
mapActions的数组写法,数组写法要保证名字一样
...mapActions(['odd','wait'])
mapMutations
用于帮助生成与mutations对话的方法,即:包含$store.commit('x',x)的函数
mapMutation的对象写法,方法会调用commit去调用mutations
...mapMutations({add:'JIA',jianshao:'JIAN'}),
mapMutation的数组写法,数组写法要保证名字一样
...mapMutations(['JIA','JIAN'])
在使用mapState和mapMutation时,如果需要传递参数,那么需要在模板中绑定事件时就传递好参数,否则默认参数是事件对象