Модули
Из-за использования единого дерева состояний все состояния нашего приложения содержатся внутри одного большого объекта. Однако по мере роста масштаба приложения хранилище может сильно раздуться.
Для решения этой проблемы Vuex позволяет разделить наше хранилище на модули. Каждый модуль может содержать свои собственные состояния, мутации, действия, геттеры и даже вложенные модули — это фрактальная структура до самого низа:
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
Локальное состояние модуля
Внутри мутаций и геттеров модуля первым аргументом будет локальное состояние модуля.
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// `state` is the local module state
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
Аналогично, внутри действий модуля context.state будет доступно локальное состояние, а состояние корня будет доступно как context.rootState.
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
Кроме того, внутри геттеров модуля корневое состояние будет доступно в качестве их третьего аргумента:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
Именование пространств
По умолчанию действия и мутации всё ещё регистрируются под глобальным именем пространства — это позволяет нескольким модулям реагировать на один и тот же тип действия/мутации. Геттеры также по умолчанию регистрируются в глобальном пространстве имён. Однако это пока не имеет функционального значения (так как есть для предотвращения внесения изменений). Необходимо быть осторожным, чтобы не определить два геттера с одинаковым именем в разных, не имеющих пространств имён, модулях, так как это приведёт к ошибке.
Если вы хотите, чтобы ваши модули были более самодостаточными или переиспользуемыми, вы можете пометить их как имеющие пространство имён с помощью namespaced: true. При регистрации модуля все его геттеры, действия и мутации будут автоматически иметь пространство имён, основанное на пути регистрации модуля. Например:
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']
}
}
}
}
}
})
Геттеры и действия с именованным пространством имён будут получать локальные getters, dispatch и commit. Другими словами, вы можете использовать ресурсы модуля, не записывая префикс в том же модуле. Переключение между именованным пространством имён и без него не влияет на код внутри модуля.
Доступ к глобальным ресурсам в именованных модулях
Если вы хотите использовать глобальное состояние и геттеры, rootState и rootGetters передаются в качестве третьего и четвёртого аргументов функциям геттеров, а также доступны в качестве свойств объекта context, переданного функциям действий.
Для отправки действий или применения мутаций в глобальном пространстве имён передайте { 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) { ... }
}
}
}
Регистрация глобального действия в именованных модулях
Если вы хотите зарегистрировать глобальные действия в именованных модулях, вы можете пометить их с помощью root: true и разместить определение действия в функции 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, модуль регистрируется, действия, мутации и геттеры добавляются в хранилище, но состояние не добавляется. Предполагается, что состояние вашего хранилища уже содержит состояние этого модуля, и вы не хотите его перезаписывать.
Переиспользование модулей
Иногда нам может потребоваться создать несколько экземпляров модуля, например:
- Создание нескольких хранилищ, использующих один и тот же модуль (например, для избегания состоятельных синглетонов в SSR, когда параметр
runInNewContextимеет значениеfalseили'once'); - Регистрация одного и того же модуля несколько раз в одном хранилище.
Если мы используем обычный объект для объявления состояния модуля, то этот объект состояния будет использоваться по ссылке и приведёт к загрязнению состояния между хранилищами/модулями при его изменении.
Это та же самая проблема, что и с data внутри компонентов Vue. Поэтому решение тоже одинаковое — используйте функцию для объявления состояния модуля (поддерживается начиная с версии 2.3.0):
const MyReusableModule = {
state: () => ({
foo: 'bar'
}),
// mutations, actions, getters...
}
© 2015–present Evan You
Licensed under the MIT License.
https://next.vuex.vuejs.org/guide/modules.html