что такое векс
Vuex — это **модель управления состоянием**, специально разработанная для приложений Vue.js. Он использует централизованное хранилище для управления состоянием всех компонентов приложения и использует соответствующие правила для обеспечения предсказуемого изменения состояния.
Что такое «модель управления государством»?
Во-первых, давайте посмотрим на следующий пример
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})
Это заявление о государственном самоуправлении состоит из следующих частей:
- состояние, источник данных, управляющий приложением;
- представление, которое декларативно сопоставляет состояние представлениям;
- действия, которые представляют собой изменения состояния, вызванные пользовательским вводом в представлении.
Вот простая иллюстрация идеи «одностороннего потока данных»:
Однако простота одностороннего потока данных может быть легко нарушена, когда наше приложение сталкивается с несколькими компонентами, разделяющими состояние:
- Несколько представлений зависят от одного и того же состояния.
- Действия из разных представлений требуют изменения одного и того же состояния.
Vuex — это библиотека управления состоянием, специально разработанная для Vue.js, позволяющая использовать детальный механизм ответа данных Vue.js для эффективного обновления состояния.
Когда мне следует использовать Vuex?
Vuex помогает нам управлять общим состоянием и поставляется с большим количеством концепций и фреймворков. Это требует компромисса между краткосрочными и долгосрочными выгодами. Использование Vuex может быть утомительным и избыточным, если вы не планируете разрабатывать большие одностраничные приложения. Это правда — если ваше приложение достаточно простое, вам лучше не использовать Vuex. Для того, что вам нужно, достаточно простого шаблона магазина. Однако, если вам нужно создать одностраничное приложение среднего и крупного масштаба, вы, вероятно, задумаетесь о том, как лучше управлять состоянием вне компонентов, и Vuex будет естественным выбором.
Самый простой магазин
конкретная реализация кода
const store = new Vuex.Store({
state: {
token: 'xxx12345'
},
mutations: {
changeToken(state, token) {
state.token = token
}
}
})
// 通过mutation去改变token的状态值
store.commit('changeToken', 'xxxx12345555')
Опять же, мы отправили через мутацию, а не напрямую изменили store.state.count, потому что мы хотим более четко отслеживать изменение статуса. Это простое соглашение может сделать ваши намерения более очевидными, чтобы вам было легче интерпретировать изменение внутреннего состояния приложения при чтении кода. Кроме того, это также дает нам возможность добиться того, что некоторые из них могут записывать каждое изменение состояния, сохранять моментальный снимок состояния инструментов отладки. С его помощью мы можем даже реализовать опыт отладки, подобный шаттлу времени.
Поскольку состояние в хранилище является реактивным, вызвать состояние в хранилище в компоненте так же просто, как вернуть его в вычисляемом свойстве. Запуск изменений — это также просто отправка мутаций в методы компонента.
основные концепции vuex
State
** Дерево с одним состоянием ** Vuex использует одно дерево состояний — да, один объект содержит все состояния уровня приложения. Пока он существует как «Единый источник данных (SSOT)». Это также означает, что каждое приложение будет содержать только один экземпляр хранилища. Единое дерево состояний позволяет нам напрямую находить любую конкретную часть состояния и легко делать моментальный снимок всего текущего состояния приложения во время отладки.
Деревья с одним состоянием не конфликтуют с модульностью — в последующих главах мы обсудим, как распределять события состояния и изменения состояния по подмодулям.
** Получить состояние Vuex в компоненте Vue **
Итак, как мы можем отображать состояние в компонентах Vue? Поскольку хранилище состояний Vuex является реактивным, самый простой способ прочитать состояние из экземпляра хранилища — вернуть некоторое состояние в вычисляемом свойстве:
// 创建一个 tree 组件
const trees = {
template: `<div>{{ tree }}</div>`,
computed: {
tree () {
return store.state.tree
}
}
}
Всякий раз, когда изменяется store.state.tree, вычисляемое свойство переоценивается, а связанная модель DOM обновляется.
Однако этот шаблон приводит к тому, что компоненты полагаются на синглтоны глобального состояния. В модульной системе сборки вам нужно часто импортировать каждый компонент, который должен использовать состояние, и вам нужно имитировать состояние при тестировании компонентов.
Vuex предоставляет механизм «внедрения» состояния из корневого компонента в каждый дочерний компонент с помощью параметра хранилища (необходимо вызвать Vue.use(Vuex)):
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
components: { trees },
template: `
<div class="app">
<trees></trees>
</div>
`
})
При регистрации параметра хранилища в корневом экземпляре экземпляр хранилища будет внедрен во все дочерние компоненты корневого компонента, и доступ к дочерним компонентам можно будет получить через this.$store . Обновим реализацию деревьев:
const trees = {
template: `<div>{{ tree }}</div>`,
computed: {
count () {
return this.$store.state.tree
}
}
}
Getter
Иногда нам нужно получить какое-то состояние из состояния в хранилище, например фильтрация и подсчет списка:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
Если несколько компонентов должны использовать это свойство, мы либо дублируем функцию, либо извлекаем ее в общую функцию и импортируем в несколько мест — ни один из способов не идеален.
Vuex позволяет нам определять «геттеры» (которые можно рассматривать как вычисляемые свойства хранилища) в хранилище. Как и вычисляемые свойства, возвращаемое значение метода получения кэшируется на основе его зависимостей и пересчитывается только при изменении его зависимостей.
Getter принимает состояние в качестве своего первого параметра:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
** Доступ через свойства ** Геттеры выставляются как объекты store.getters, и вы можете получить доступ к этим значениям как к свойствам:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
Getter 也可以接受其他 getter 作为第二个参数:
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1
我们可以很容易地在任何组件中使用它:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
Обратите внимание, что геттеры кэшируются как часть системы реактивности Vue при доступе через свойства.
** Доступ по методу ** Вы также можете передать аргументы геттеру, позволив геттеру вернуть функцию. Очень полезно, когда вы запрашиваете массивы в хранилище.
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
Обратите внимание, что при доступе к геттеру через метод он будет вызываться каждый раз, и результат не будет кэшироваться.
Mutation
Единственный способ изменить состояние в хранилище Vuex — отправить мутацию. Мутации в Vuex очень похожи на события: каждая мутация имеет строковый тип события (тип) и функцию обратного вызова (обработчик). В этой функции обратного вызова мы фактически изменяем состояние, и она принимает состояние в качестве первого параметра:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
state.count++
}
}
})
Вы не можете вызвать обработчик мутации напрямую. Этот вариант больше похож на регистрацию события: «При срабатывании мутации инкремента типа вызвать эту функцию».
store.commit('increment')
**Отправить полезную нагрузку**
В store.commit можно передать дополнительные параметры, а именно полезную нагрузку мутации:
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
В большинстве случаев полезной нагрузкой должен быть объект, который может содержать несколько полей и записывать мутации более читабельно:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
** Метод отправки стиля объекта **
store.commit({
type: 'increment',
amount: 10
})
При использовании коммитов в объектном стиле весь объект передается в качестве полезной нагрузки функции мутации, поэтому обработчик остается прежним:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
** Мутация должна соответствовать правилам ответа Vue ** Поскольку состояние в хранилище Vuex является реактивным, когда мы меняем состояние, компонент Vue, отслеживающий состояние, также обновляется автоматически. Это также означает, что мутации в Vuex также должны подчиняться тем же предостережениям, что и при использовании Vue:
Лучше всего заранее инициализировать все необходимые свойства в вашем магазине.
Когда вам нужно добавить новое свойство к объекту, вы должны
используйте Vue.set(obj, 'newProp', 123) или
Замените старые объекты новыми. Например, используя оператор распространения объекта этапа 3, мы можем написать:
state.obj = {...state.obj, newProp: 123} ** Используйте константы вместо типов событий Mutation ** Использование констант вместо типов событий мутации является распространенным шаблоном в различных реализациях Flux. Это позволяет работать таким инструментам, как линтеры, а наличие этих констант в отдельных файлах дает вашим соавторам кода представление о мутациях, содержащихся во всем приложении:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Вам решать, использовать константы или нет — это может быть полезно в больших проектах, требующих совместной работы нескольких человек. Но если вам это не нравится, вы можете вообще не делать этого.
** Мутация должна быть синхронной функцией ** Важно помнить, что мутации должны быть синхронизированными функциями. Почему? Пожалуйста, обратитесь к примеру ниже:
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
Теперь представьте, что мы отлаживаем приложение и наблюдая за журналом мутации Devtool. Каждая мутация зарегистрирована, и devtools должен захватить снимок предыдущего состояния и следующего состояния. Тем не менее, обратный вызов в асинхронной функции в мутации в приведенном выше примере делает это невозможное: поскольку обратный вызов не был вызван, когда мутационные пожары DEVTools не знают, когда обратный вызов на самом деле называется - по существу любые изменения в Функция обратного вызова неразрешена.
** Отправить мутацию в компоненте ** Вы можете использовать это $ Store.commit ('xxx') в компоненте или использовать вспомогательную функцию MapMutations для вызова методов в компоненте для Store.commit (вам нужно внедрить Store в корневой узел).
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
Action
Попробуйте этот урок на scrimba Действие похоже на мутацию, разница в следующем:
Действия вызывают мутации, а не изменения состояния напрямую. Действие может содержать произвольные асинхронные операции. Зарегистрируем простое действие:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Функция Action принимает объект контекста с теми же методами и свойствами, что и экземпляр хранилища, поэтому вы можете вызвать context.commit, чтобы зафиксировать мутацию, или получить состояние и геттеры через context.state и context.getters. Когда позже мы представим модули, вы увидите, почему объект контекста не является самим экземпляром хранилища.
На практике мы часто используем деструктуризацию параметров ES2015 для упрощения кода (особенно когда нам нужно много раз вызывать commit):
actions: {
increment ({ commit }) {
commit('increment')
}
}
** Распространить действие ** Действия запускаются методом store.dispatch:
store.dispatch('приращение') На первый взгляд это может показаться ненужной задачей, не было бы нам удобнее распространять мутации напрямую? Не совсем, помните ограничение, что мутации должны выполняться синхронно? Действие не обязывает! Мы можем выполнять асинхронные операции внутри действия:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
Действия поддерживают ту же полезную нагрузку и распределение объектов:
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
Давайте рассмотрим более практичный пример корзины покупок, включающий вызов асинхронного API и отправку нескольких мутаций:
actions: {
checkout ({ commit, state }, products) {
// 把当前购物车的物品备份起来
const savedCartItems = [...state.cart.added]
// 发出结账请求,然后乐观地清空购物车
commit(types.CHECKOUT_REQUEST)
// 购物 API 接受一个成功回调和一个失败回调
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
Обратите внимание, что мы выполняем серию асинхронных операций и записываем побочные эффекты (то есть изменения состояния) действия, отправляя мутации.
** Распределить действие в компоненте ** Вы используете this.$store.dispatch('xxx') для отправки действий в вашем компоненте или используете помощник mapActions для сопоставления методов вашего компонента с вызовами store.dispatch (сначала вам нужно внедрить хранилище в корневой узел):
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
** Комбинированное действие ** Действия обычно асинхронны, так как же узнать, что действие завершилось? Что еще более важно, как мы можем объединить несколько действий для обработки более сложных асинхронных процессов?
Во-первых, нужно понимать, что store.dispatch может обрабатывать промис, возвращаемый обработчиком сработавшего действия, а store.dispatch по-прежнему возвращает промис:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
Теперь вы можете:
store.dispatch('actionA').then(() => {
// ...
})
在另外一个 action 中也可以:
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
Наконец, если мы воспользуемся преимуществами async/await, мы можем составить действия следующим образом:
// Предположим, что getData() и getOtherData() возвращают Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
Store.dispatch может запускать несколько функций действия в разных модулях. В этом случае возвращенный промис будет выполняться только после завершения всех запускающих функций.
Module
Благодаря использованию единого дерева состояний все состояния приложения будут агрегированы в относительно большой объект. Когда приложение становится очень сложным, объект хранилища может сильно раздуться.
Чтобы решить вышеуказанные проблемы, Vuex позволяет нам разделить хранилище на модули. Каждый модуль имеет свое состояние, мутации, действия, геттеры и даже вложенные подмодули — разбитые таким же образом сверху вниз:
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 的状态
** Локальное состояние модуля **
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 这里的 `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 = 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']
}
}
}
}
}
})
Геттеры и действия с поддержкой пространства имен получают локализованные геттеры, отправки и фиксации. Другими словами, вам не нужно добавлять префикс к именам дополнительных пространств в одном и том же модуле при использовании ресурсов модуля. Нет необходимости изменять код внутри модуля после изменения атрибута пространства имен.
**Доступ к глобальным активам в модуле с пространством имен** Если вы хотите использовать глобальное состояние и геттер, rootState и rootGetter передаются геттеру в качестве третьего и четвертого параметров, а также действию через свойства объекта контекста.
Чтобы отправить действия или зафиксировать мутации в глобальном пространстве имен, передайте { root: true } в качестве третьего параметра для отправки или фиксации.
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) { ... }
}
}
}
** Зарегистрируйте глобальные действия в модулях с пространством имен ** Чтобы зарегистрировать глобальное действие в модуле с пространством имен, вы можете добавить root: true и поместить определение действия в обработчик функции. Например:
{
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'
])
}
}
** Примечание для разработчиков плагинов ** Если вы разрабатываете подключаемый модуль (Plugin), который предоставляет модули и позволяет пользователям добавлять их в хранилище Vuex, вам может потребоваться учитывать имя пространства модуля. В этом случае вы можете разрешить пользователю указывать имя пространства через объект параметра плагина:
// Получить имя пространства через объект параметра плагина // затем вернемся к функции плагина Vuex
export function createPlugin (options = {}) {
return function (store) {
// 把空间名字添加到插件模块的类型(type)中去
const namespace = options.namespace || ''
store.dispatch(namespace + 'pluginAction')
}
}
** Модуль динамической регистрации ** После создания магазина вы можете зарегистрировать модуль с помощью метода store.registerModule:
// регистрируем модульmyModule
store.registerModule('myModule', {
// ...
})
// регистрация вложенных модулейnested/myModule
store.registerModule(['вложенный', 'моймодуль'], {
// ...
})
Затем к состоянию модуля можно получить доступ через store.state.myModule и store.state.nested.myModule.
Функция динамической регистрации модулей позволяет другим плагинам Vue управлять состоянием с помощью Vuex, добавляя новые модули в хранилище. Например, плагин vuex-router-sync объединяет vue-router и vuex через модуль динамической регистрации для управления состоянием маршрутизации приложений.
Вы также можете использовать store.unregisterModule(moduleName) для динамической выгрузки модулей. Обратите внимание, что вы не можете выгрузить статические модули (т. е. модули, объявленные при создании хранилища) с помощью этого метода.
** сохранить состояние ** При регистрации нового модуля вы, скорее всего, захотите сохранить предыдущее состояние, например, из приложения, отображаемого сервером. Вы можете заархивировать его с помощью параметра saveState: store.registerModule('a', module, {preservState: true}).
Когда вы устанавливаете saveState: true , модуль будет зарегистрирован, а действия, мутации и геттеры будут добавлены в хранилище, а состояние — нет. Это предполагает, что состояние хранилища уже содержит состояние модуля, и вы не хотите его перезаписывать.
** Повторное использование модуля ** Иногда нам может понадобиться создать несколько экземпляров модуля, например:
Создайте несколько хранилищ, которые используют один и тот же модуль (например, чтобы избежать синглтонов с отслеживанием состояния при рендеринге на сервере, когда параметр runInNewContext имеет значение false или 'once' ) Многократная регистрация одного и того же модуля в магазине Если мы используем чистый объект для объявления состояния модуля, то этот объект состояния будет совместно использоваться по ссылке, что приведет к проблеме загрязнения данных между хранилищами или модулями при изменении объекта состояния.
На самом деле это та же проблема с данными внутри компонентов Vue. Таким образом, решение такое же - используйте функцию для объявления состояния модуля (только 2.3.0+):
const MyReusableModule = {
state () {
return {
foo: 'bar'
}
},
// mutation, action 和 getter 等等...
}