模块
由于使用单个状态树,我们应用程序的所有状态都包含在一个大型对象中。但是,随着应用程序规模的增长,存储可能会变得非常臃肿。
为了解决这个问题,Vuex 允许我们将存储划分为 **模块**。每个模块都可以包含自己的状态、Mutation、Action、Getter,甚至嵌套模块——它一直都是分形的。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> `moduleA`'s state
store.state.b // -> `moduleB`'s state
模块局部状态
在模块的 Mutation 和 Getter 中,接收到的第一个参数将是 **模块的局部状态**。
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// `state` is the local module 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,这会导致错误。
如果您希望模块更加独立或可重用,可以使用 namespaced: true
将其标记为命名空间。当模块注册时,它的所有 Getter、Action 和 Mutation 将根据模块注册的路径自动进行命名空间化。例如
const store = createStore({
modules: {
account: {
namespaced: true,
// module assets
state: () => ({ ... }), // module state is already nested and not affected by namespace option
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// nested modules
modules: {
// inherits the namespace from parent module
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// further nest the namespace
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
命名空间化的 Getter 和 Action 将接收本地化的 getters
、dispatch
和 commit
。换句话说,您可以在同一个模块中使用模块资源,而无需编写前缀。在命名空间之间切换不会影响模块内部的代码。
在命名空间模块中访问全局资源
如果您想使用全局状态和 Getter,rootState
和 rootGetters
将作为第三个和第四个参数传递给 Getter 函数,并且还作为传递给 Action 函数的 context
对象上的属性公开。
要分派全局命名空间中的 Action 或提交 Mutation,将 { root: true }
作为第三个参数传递给 dispatch
和 commit
。
modules: {
foo: {
namespaced: true,
getters: {
// `getters` is localized to this module's getters
// you can use rootGetters via 4th argument of getters
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// dispatch and commit are also localized for this module
// they will accept `root` option for the root dispatch/commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
rootGetters['bar/someGetter'] // -> 'bar/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
}),
...mapGetters([
'some/nested/module/someGetter', // -> this['some/nested/module/someGetter']
'some/nested/module/someOtherGetter', // -> this['some/nested/module/someOtherGetter']
])
},
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
}),
...mapGetters('some/nested/module', [
'someGetter', // -> this.someGetter
'someOtherGetter', // -> this.someOtherGetter
])
},
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: {
// look up in `some/nested/module`
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// look up in `some/nested/module`
...mapActions([
'foo',
'bar'
])
}
}
插件开发者的注意事项
当您创建一个 插件 来提供模块并让用户将它们添加到 Vuex 存储中时,您可能关心模块的不可预测的命名空间。如果插件用户在命名空间模块下添加您的模块,您的模块也将被命名空间化。为了适应这种情况,您可能需要通过插件选项接收命名空间值。
// get namespace value via plugin option
// and returns Vuex plugin function
export function createPlugin (options = {}) {
return function (store) {
// add namespace to plugin module's types
const namespace = options.namespace || ''
store.dispatch(namespace + 'pluginAction')
}
}
动态模块注册
您可以在创建存储 **之后** 使用 store.registerModule
方法注册模块。
import { createStore } from 'vuex'
const store = createStore({ /* options */ })
// register a module `myModule`
store.registerModule('myModule', {
// ...
})
// register a nested module `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
模块的状态将作为 store.state.myModule
和 store.state.nested.myModule
公开。
动态模块注册使其他 Vue 插件也可以通过将模块附加到应用程序的存储来利用 Vuex 进行状态管理。例如,vuex-router-sync
库通过在动态附加的模块中管理应用程序的路由状态,将 vue-router 与 vuex 集成。
您还可以使用 store.unregisterModule(moduleName)
删除动态注册的模块。请注意,您无法使用此方法删除静态模块(在创建存储时声明)。
请注意,您可以通过 store.hasModule(moduleName)
方法检查模块是否已注册到存储中。需要注意的是,嵌套模块应作为数组传递给 registerModule
和 hasModule
,而不是作为指向模块路径的字符串。
保留状态
您可能希望在注册新模块时保留以前的状态,例如保留来自服务器端渲染应用程序的状态。您可以使用 preserveState
选项实现这一点:store.registerModule('a', module, { preserveState: true })
当您设置 preserveState: true
时,模块将被注册,Action、Mutation 和 Getter 将被添加到存储中,但状态不会被添加。假设您的存储状态已经包含该模块的状态,并且您不想覆盖它。
模块重用
有时我们可能需要创建模块的多个实例,例如
- 创建使用相同模块的多个存储(例如,当
runInNewContext
选项为false
或'once'
时,避免 SSR 中有状态的单例); - 在同一个存储中多次注册同一个模块。
如果我们使用普通对象来声明模块的状态,那么该状态对象将被引用共享,并且当它被修改时会导致跨存储/模块状态污染。
这实际上与 Vue 组件中的 data
存在完全相同的问题。因此,解决方案也是一样的——使用函数来声明模块状态(在 2.3.0+ 中支持)。
const MyReusableModule = {
state: () => ({
foo: 'bar'
}),
// mutations, actions, getters...
}