Módulos

Devido ao uso de uma única árvore de estado, todo o estado de nossa aplicação está contido dentro de um grande objeto. No entanto, à medida que nosso aplicativo cresce em escala, o store pode ficar realmente inchado.

Para ajudar com isso, o Vuex nos permite dividir nosso store em módulos. Cada módulo pode conter seu próprio estado, mutações, ações, getters e até módulos aninhados - é todo o complexo caminho abaixo:

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`'s state
store.state.b // -> `moduleB`'s state

Estado Local do Módulo

Dentro das mutações e getters de um módulo, o 1º argumento recebido será o estado local do módulo.

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // `state` é o estado local do módulo
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

Da mesma forma, dentro das ações do módulo, context.state irá expor o estado local, e o estado raiz será exposto como context.rootState:

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

Além disso, dentro do módulo getters, o estado raiz será exibido como seu 3º argumento:

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

Namespacing

Por padrão, ações, mutações e getters dentro dos módulos ainda são registrados sob o namespace global - isso permite que vários módulos reajam ao mesmo tipo de ação/mutação.

Se você quer que seus módulos sejam mais independentes ou reutilizáveis, você pode marcá-los como namespaced com namespaced: true. Quando o módulo é registrado, todos os getters, ações e mutações serão automaticamente namespaced com base no caminho no qual o módulo está registrado. Por exemplo:

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

      // module assets
      state: { ... }, // o estado do módulo já está aninhado e não é afetado pela opção de namespace
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },

      // módulos aninhados
      modules: {
        // herda o namespace do modulo pai
        myPage: {
          state: { ... },
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },

        // aninhar ainda mais o namespace
        posts: {
          namespaced: true,

          state: { ... },
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }
})

Os getters e as ações namespaced receberão getters, dispatch e commit localizados. Em outras palavras, você pode usar os recursos do módulo sem escrever prefixo no mesmo módulo. Alternar entre namespaced ou não, não afeta o código dentro do módulo.

Acessando Recursos Globais em Módulos Namespaced

Se você quiser usar estado global e getters, rootState e rootGetters são passados como o 3º e 4º argumentos para funções getter, e também expostos como propriedades no objeto context passado para funções de ação.

Para despachar ações confirmar (ou fazer um commit de) mutações no namespace global, passe {root: true} como o 3º argumento para dispatch e commit.

modules: {
  foo: {
    namespaced: true,

    getters: {
      // `getters` está localizado nos getters deste módulo
      // você pode usar rootGetters como 4º argumento de getters
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // dispatch e commit também estão localizados para este módulo
      // eles aceitarão a opção `root` para o dispatch/commit da raiz
      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) { ... }
    }
  }
}

Registrar Ação Global em Módulos com Namespaces

Se você quiser registrar ações globais em módulos com namespaces, você pode marcá-lo com root: true e colocar a definição de ação na função handler. Por exemplo:

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,

      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}

Usando Métodos Auxiliares com Namespace

Ao vincular um módulo com namespace aos componentes com os auxiliares mapState, mapGetters, mapActions e mapMutations, ele pode ficar um pouco verboso:

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']()
  ])
}

Nesses casos, você pode passar a String do namespace do módulo como o 1º argumento para os auxiliares, de modo que todas as ligações sejam feitas usando esse módulo como o contexto. O acima pode ser simplificado para:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

Além disso, você pode criar auxiliares com namespace usando createNamespacedHelpers. Ele retorna um objeto com novos métodos auxiliares dos componentes vinculados ao valor do namespace fornecido:

import { createNamespacedHelpers } from 'vuex'

const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')

export default {
  computed: {
    // procure em `some/nested/module`
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // procure em `some/nested/module`
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

Advertência para Desenvolvedores de Plug-ins

Você pode se preocupar com espaços de nomes imprevisíveis para seus módulos quando você cria um plugin que fornece os módulos e permite que os usuários os incluam em um store Vuex. Seus módulos também serão namespaced se os usuários do plugin adicionarem seus módulos em um módulo namespaced. Para adaptar esta situação, você pode precisar receber um valor de namespace através de uma opção sua no plugin:

// obtem valor do namespace via opção no plugin
// e retorna a função plugin do Vuex
export function createPlugin (options = {}) {
  return function (store) {
    // add namespace to plugin module's types
    // adicionar namespace aos tipos de módulo do plug-in
    const namespace = options.namespace || ''
    store.dispatch(namespace + 'pluginAction')
  }
}

Registro de Módulo Dinâmico

Você pode registrar um módulo após o store ser criado com o método store.registerModule:

// registra um módulo `myModule`
store.registerModule('myModule', {
  // ...
})

// registra um módulo aninhado `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})

Os estados do módulo serão expostos como store.state.myModule e store.state.nested.myModule.

O registro de módulo dinâmico possibilita que outros plug-ins Vue aproveitem também o Vuex para gerenciamento de estado, anexando um módulo ao store do aplicativo. Por exemplo, a biblioteca vuex-router-sync integra o vue-router com o vuex gerenciando o estado da rota do aplicativo em um módulo conectado dinamicamente.

Você também pode remover um módulo dinamicamente registrado com o store.unregisterModule(moduleName). Note que você não pode remover módulos estáticos (declarados na criação do store) com este método.

Preservando o estado

É bem provável que você queira preservar o estado anterior ao registrar um novo módulo, como preservar o estado de um aplicativo Renderizado no Lado do Servidor (Server Side Rendered). Você pode fazer isso com a opção preserveState:store.registerModule('a', module, {preserveState: true})

Quando você informa preserveState: true, o módulo é registrado, as ações, mutações e getters são incluídos no store, mas o estado não. É assumido que estado da sua store já contém um estado para aquele módulo e você não quer sobrescrevê-lo.

Reutilização do Módulo

Às vezes, podemos precisar criar várias instâncias de um módulo, por exemplo:

Se usarmos um objeto simples para declarar o estado do módulo, esse objeto de estado será compartilhado por referência e causará poluição entre estados de store/módulo quando ele sofrer uma mutação.

Este é exatamente o mesmo problema com data dentro dos componentes Vue. Então, a solução também é a mesma - use uma função para declarar o estado do módulo (suportado em 2.3.0+):

const MyReusableModule = {
  state () {
    return {
      foo: 'bar'
    }
  },
  // mutações, ações, getters...
}