Модули

Из-за использования единого дерева состояния, все глобальные данные приложения оказываются помещены в один большой объект. По мере роста приложения, хранилище может существенно раздуться.

Чтобы помочь в этой беде, 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');
      }
    }
  }
};

Кроме того, в геттеры корневое состояние передаётся 3-м параметром:

const moduleA = {
  // ...
  getters: {
    sumWithRootCount(state, getters, rootState) {
      return state.count + rootState.count;
    }
  }
};

Пространства имён

По умолчанию действия, мутации и геттеры внутри модулей регистрируются в глобальном пространстве имён — это позволяет нескольким модулям реагировать на тот же тип мутаций/действий.

Если вы хотите сделать модули более самодостаточными и готовыми для переиспользования, вы можете создать его с собственным пространством имён, указав опцию namespaced: true. Когда модуль будет зарегистрирован, все его геттеры, действия и мутации будут автоматически связаны с этим пространством имён, основываясь на пути, по которому зарегистрирован модуль. Например:

const store = new Vuex.Store({
  modules: {
    account: {
      namespaced: true,

      // содержимое модуля
      state: () => ({ ... }), // состояние модуля автоматически вложено и не зависит от опции пространства имён
      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']
          }
        }
      }
    }
  }
})

Геттеры и действия с собственным пространством имён будут получать свои локальные getters, dispatch и commit. Другими словами, вы можете использовать содержимое модуля без написания префиксов в том же модуле. Переключения между пространствами имён не влияет на код внутри модуля.

Доступ к глобальному содержимому в модулях со своим пространством имён

Если вы хотите использовать глобальное состояние и геттеры, rootState и rootGetters передаются 3-м и 4-м аргументами в функции геттеров, а также как свойства в объекте context, передаваемом в функции действий.

Для запуска действий или совершения мутаций в глобальном пространстве имён нужно добавить { root: true } 3-м аргументом в dispatch и commit.

modules: {
  foo: {
    namespaced: true,

    getters: {
      // `getters` ограничены геттерами данного модуля
      // вы можете использовать rootGetters из 4-го аргумента геттеров
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // dispatch и commit также ограничены данным модулем
      // они принимают опцию `root` для вызова в глобальном пространстве имён
      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 и поместить определение действия в функцию 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'])
  }
};

Уточнение для разработчиков плагинов

Вас может обеспокоить непредсказуемость пространства имён для ваших модулей, когда вы создаёте плагин с собственными модулями и возможностью пользователям добавлять их в хранилище Vuex. Ваши модули будут также помещены в пространство имён, если пользователи плагина добавляют ваши модули в модуль со своим пространством имён. Чтобы приспособиться к этой ситуации, вам может потребоваться получить значение пространства имён через настройки плагина:

// получение значения пространства имён через options
// и возвращение функции плагина Vuex
export function createPlugin(options = {}) {
  return function(store) {
    // добавление пространства имён к модулям плагина
    const namespace = options.namespace || '';
    store.dispatch(namespace + 'pluginAction');
  };
}

Динамическая регистрация модулей

Вы можете зарегистрировать модуль уже и после того, как хранилище было создано, используя метод 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.

Динамическая регистрация модулей позволяет другим плагинам Vue также использовать Vuex для управления своим состоянием, добавляя модуль к хранилищу данных приложения. Например, библиотека vuex-router-sync интегрирует vue-router во vuex, отражая изменение текущего пути приложения в динамически присоединённом модуле.

Удалить динамически зарегистрированный модуль можно с помощью store.unregisterModule(moduleName). Обратите внимание, что статические (определённые на момент создания хранилища) модули при помощи этого метода удалить не получится.

Обратите внимание, что можно проверить, зарегистрирован ли уже модуль с заданным именем с помощью метода store.hasModule(moduleName).

Сохранение состояния

Вероятно, вы хотите сохранить предыдущее состояние при регистрации нового модуля, например сохранить состояние из приложения с рендерингом на стороне сервера. Вы можете этого добиться с помощью опции preserveState: store.registerModule('a', module, { preserveState: true }).

При использовании preserveState: true модуль регистрируется, действия, мутации и геттеры добавляются в хранилище, а состояние нет. Предполагается, что состояние вашего хранилища уже содержит состояние для этого модуля и нет необходимости его перезаписывать.

Повторное использование модулей

Иногда нам может потребоваться создать несколько экземпляров модуля, например:

Если мы используем просто объект для определения состояния модуля, тогда этот объект состояния будет использоваться по ссылке и вызывать загрязнение состояния хранилища / модуля при его мутациях.

Это фактически та же самая проблема с data внутри компонентов Vue. Таким образом решение будет таким же — использовать функцию для объявления состояния модуля (поддержка добавлена в версии 2.3.0+):

const MyReusableModule = {
  state: () => ({
    foo: 'bar'
  })
  // мутации, действия, геттеры...
};