Olá Habr! Apresento a você a tradução do artigo “Mastering Vuex - Zero to Hero”, de Sanath Kumar.
A documentação oficial da Vuex define como um padrão de gerenciamento de estado + biblioteca para aplicativos Vue.js. Mas o que isso significa? O que é um padrão de gerenciamento de estado?
Imagine que você está trabalhando em um aplicativo Web grande com centenas de rotas e componentes. Não seria mais fácil se pudéssemos armazenar todos os dados que precisaríamos em um aplicativo em um armazenamento centralizado?

Cada componente ou rota em nosso aplicativo solicitará dados do estado Vuex e transferirá os dados alterados de volta ao estado.
Em essência, o estado da Vuex pode ser visto como a única fonte de verdade para toda a aplicação.
Os dados são armazenados dentro do estado como um objeto JSON. Por exemplo:
state: { name: "John Doe", age: "28" }
Mas como nossos componentes e rotas podem acessar dados armazenados em nosso estado? Para fazer isso, precisamos definir getters dentro de nosso repositório Vuex que retornarão dados do repositório para nossos componentes. Vamos ver como é um getter simples, que recebe o nome do nosso repositório:
getters: { NAME: state => { return state.name; }, }
Observe que o nome do getter está em letras maiúsculas. Esta é apenas uma recomendação de estilo de código. Não é necessário segui-lo se você não gostar.
Agora que definimos um getter para o nome, é incrivelmente fácil obter o valor do nome dentro do nosso componente. O código abaixo permite que você faça isso.
let name = this.$store.getters.NAME;
Nós descobrimos como obter dados do armazenamento. Agora vamos ver como podemos definir os dados no repositório. Vamos definir setters, certo? Além disso, os setters Vuex são nomeados de maneira um pouco diferente. Definimos uma mutação para definir dados para o nosso estado Vuex.
mutations: { SET_NAME: (state, payload) => { state.name = payload; }, }
O que mais é carga útil? Carga útil é os dados transmitidos à nossa mutação a partir do componente que a produz. Como podemos fazer isso? Muito simples:
this.$store.commit('SET_NAME', your_name);
Este trecho de código mudará o estado do aplicativo e definirá qualquer valor atribuído ao seu_nome para a propriedade name dentro de nosso repositório.
MUTAÇÕES SINCRÔNICAS
Imagine que temos uma lista de nomes armazenados em um banco de dados em um servidor remoto. O servidor nos fornece um terminal que retorna uma matriz de nomes que podem ser usados em nosso Vue.js. Obviamente, podemos usar o Axios para consultar o terminal e obter os dados.
let {data} = await Axios.get('https://myapiendpoint.com/api/names');
Depois disso, podemos passar a matriz retornada para o estado Vuex da nossa loja usando uma mutação. Fácil né? Mas na verdade não. Mutações são síncronas e não podemos executar operações assíncronas, como chamadas de API, dentro de uma mutação.
O que devemos fazer então? Crie ações .
Ações são como mutações, mas em vez de mudar diretamente o estado, elas fazem uma mutação. Parece confuso? Vejamos o anúncio da ação.
actions: { SET_NAME: (context, payload) { context.commit('SET_NAME', payload); }, }
Definimos uma ação chamada SET_NAME que leva o contexto e a carga útil como parâmetros. A ação confirma a mutação SET_NAME, criada anteriormente, com os dados passados para ela, ou seja, seu_nome .
Agora, em vez de invocar a mutação diretamente, nossos componentes acionam a ação SET_NAME com um novo nome como dados, da seguinte maneira:
this.$store.dispatch('SET_NAME', your_name);
Em seguida, a ação inicia a mutação com os dados passados para ela, ou seja, seu nome.
Mas porque?
Você pode estar se perguntando por que uma declaração de ação é necessária se podemos simplesmente iniciar mutações com um novo valor diretamente de nossos componentes. Como mencionado acima, as mutações são síncronas, mas nenhuma ação.
No exemplo acima, o caso é considerado quando você precisa atualizar o valor do nome, mas não apenas em seu estado, mas também no banco de dados em execução no servidor remoto. Estou certo de que é assim que você pretende usar o Vuex em um projeto real em 99% dos casos. Dê uma olhada no seguinte trecho de código:
mutations: { SET_NAME: (state, name) => { state.name = name; }, }, actions: { SET_NAME: async (context, name) => { let {data} = await Axios.post('http://myapiendpoint.com/api/name', {name: name}); if (data.status == 200) { context.commit('SET_NAME', name); } }, }
O código em si é auto-explicativo. Usamos o Axios para enviar o nome ao terminal. Se a solicitação POST foi bem-sucedida e o valor do nome do campo foi alterado com sucesso no servidor, iniciamos a mutação SET_ NAME para atualizar o valor do nome dentro do nosso estado.
TOMAR PRÁTICA NUNCA INICIAR MUTAÇÕES DIRETAMENTE. PARA ISSO SEMPRE USE AÇÕES.
Configurando o armazenamento Vuex no Vue.JS
Vamos nos aprofundar e descobrir como podemos implementar o Vuex em um aplicativo real.
Etapa 1. Instale o Vuex
npm install --save vuex
Etapa 2. Criando um repositório Vuex
- Crie o diretório da loja na raiz do nosso aplicativo.
- Crie o arquivo index.js neste diretório e use o código abaixo para criar um novo repositório.
import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export const store = new Vuex.Store({ state: {}, getters: {}, mutations: {}, actions: {}, });
Etapa 3. Adicionando armazenamento Vuex ao aplicativo Vue.JS
1. Importe o repositório para o arquivo main.js.
import {store} from './store';
2. Adicione armazenamento à instância do Vue, como mostrado abaixo:
new Vue({ el: '#app', store, router, render: h => h(App), });
Agora podemos adicionar variáveis de estado, getters, mutações e ações ao nosso repositório Vuex.
Exemplo
Dê uma olhada no repositório Vuex de um aplicativo simples da lista de tarefas. "Não é apenas mais uma lista de tarefas !!!". Hein? Não se preocupe. No final deste artigo, você aprenderá como usar toda a potência do Vuex.
import Vue from 'vue'; import Vuex from 'vuex'; import Axios from 'axios'; Vue.use(Vuex); export const store = new Vuex.Store({ state: { todos: null, }, getters: { TODOS: state => { return state.todos; }, }, mutations: { SET_TODO: (state, payload) => { state.todos = payload; }, ADD_TODO: (state, payload) => { state.todos.push(payload); }, }, actions: { GET_TODO: async (context, payload) => { let {data} = await Axios.get('http://yourwebsite.com/api/todo'); context.commit('SET_TODO', data); }, SAVE_TODO: async (context, payload) => { let {data} = await Axios.post('http://yourwebsite.com/api/todo'); context.commit('ADD_TODO', payload); }, }, });
Adicionar um novo item à lista de tarefas
Dentro do seu componente, inicie a ação SAVE_TODO passando um novo item de tarefa, conforme mostrado no snippet de código abaixo.
let item = 'Get groceries'; this.$store.dispatch('SAVE_TODO', item);
A ação SAVE_TODO faz uma solicitação POST para o terminal e inicia a mutação ADD_TODO , que adiciona um item de tarefa à variável de estado todos .
Obtendo itens de tarefas
Dentro do bloco mounted () do seu componente, inicie a segunda ação GET_TODO , que recebe todos os itens de pendências do nó de extremidade e os armazena na variável de estado todos , iniciando a mutação SET_TODO:
mounted() { this.$store.dispatch('GET_TODO'); }
Acessar itens de tarefas pendentes dentro de um componente
Para acessar o elemento todos dentro de um componente, crie uma propriedade computada:
computed: { todoList() { return this.$store.getters.TODOS; }, }
Dentro do componente, você pode acessar a propriedade computada:
<div class="todo-item" v-for="item in todoList"></div>
Usando o método mapGetters
Existe uma maneira ainda mais fácil de acessar itens de tarefas em um componente usando o método mapGetters fornecido pela Vuex.
import {mapGetters} from 'vuex'; computed : { ...mapGetters(['TODOS']), // }
Você já deve ter adivinhado que o código dentro do modelo deve ser alterado, conforme mostrado no snippet abaixo.
<div class="todo-item" v-for="item in TODOS"></div>
Observe como usamos o operador de distribuição ES6 [...] dentro de nossas propriedades calculadas.
O ARMAZENAMENTO VUEX NÃO É APENAS A FONTE DO ESTADO ATUAL DO SEU APLICATIVO. É TAMBÉM O ÚNICO PONTO QUE DEVE MUDAR ESTE ESTADO.
Isso requer uma pequena explicação. Já aprendemos como criar ações para receber e instalar itens de tarefas em nosso repositório. E se precisarmos atualizar um elemento e marcá-lo? Onde executamos o código para isso?
Na Internet, você pode encontrar opiniões diferentes sobre esse assunto. A documentação também não possui orientações claras sobre isso.
Eu recomendaria armazenar todas as chamadas de API dentro de ações no seu repositório Vuex. Assim, cada alteração de estado ocorre apenas dentro do repositório, facilitando a depuração e simplificando o entendimento do código, além de facilitar a edição do código.
Organização do código
Salvar todas as variáveis de estado, getters, ações e mutações em um arquivo rapidamente o tornará complicado assim que você começar a trabalhar com aplicativos grandes. Vamos ver como você pode organizar o armazenamento em vários arquivos como módulos.
Crie um novo diretório dentro do seu repositório e denomine-o de módulos . Adicione o arquivo todos.js ao diretório criado que contém o seguinte código:
const state = {}; const getters = {}; const mutations = {}; const actions = {}; export default { state, getters, mutations, actions, };
Agora podemos mover as variáveis de estado, getters, mutações e ações do arquivo index.js para o arquivo todos.js . Lembre-se de importar o Axios . Tudo o que precisamos fazer é informar à Vuex que criamos o módulo de armazenamento e onde encontrá-lo. O arquivo index.js atualizado deve ter a seguinte aparência:
import Vue from 'vue'; import Vuex from 'vuex'; import Axios from 'axios'; import todos from './modules/todos'; Vue.use(Vuex); export const store = new Vuex.Store({ state: {}, getters: {}, mutations: {}, actions: {}, modules: { todos, }, });
O arquivo todos.js ficará assim:
import Axios from 'axios'; state = { todos: null, }; getters = { TODOS: state => { return state.todos; }, }; mutations = { SET_TODO: (state, payload) => { state.todos = payload; }, ADD_TODO: (state, payload) => { state.todos.push(payload); }, }; actions = { GET_TODO: async (context, payload) => { let {data} = await Axios.get('http://yourwebsite.com/api/todo'); context.commit('SET_TODO', data); }, SAVE_TODO: async (context, payload) => { let {data} = await Axios.post('http://yourwebsite.com/api/todo'); context.commit('ADD_TODO', payload); }, }; export default { state, getters, mutations, actions, };
Sumário
- O estado do aplicativo é armazenado como um objeto JSON grande.
- Os getters são usados para acessar valores armazenados na loja.
- Mutações atualizam sua condição. Deve-se lembrar que as mutações são síncronas.
- Todas as operações assíncronas devem ser executadas em ações . As ações mudam de estado, iniciando mutações.
- Estabeleça uma regra para iniciar mutações exclusivamente através da ação .
- Módulos podem ser usados para organizar seu armazenamento em vários arquivos pequenos.
O Vuex torna o trabalho com o Vue muito mais fácil e divertido. Se você é iniciante, pode haver situações em que é difícil decidir se deve usar o Vuex em determinadas áreas do seu aplicativo. Siga seu instinto. Você alcançará a alta velocidade rapidamente.