Vuex - Gerenciamento Centralizado de Estado no Vue.js
Cuidado
Este artigo é voltado para o Vue 2. O Vue 3 agora está disponível e é a maneira recomendada de desenvolver aplicativos com Vue.
Soluções comuns para enviar e receber dados entre páginas incluem passar dados via URL, cookies e LocalStorage. Embora funcionais, esses métodos não fornecem um padrão para manipulação de dados e não são fontes de dados reativas que se harmonizam com a reatividade do Vue.js.
Evan You, criador do Vue.js, desenvolveu uma ferramenta chamada Vuex projetada especificamente para o Vue que permite o gerenciamento centralizado de estado em aplicações web construídas com este framework. Embora não seja a única ferramenta de gerenciamento de dados, ela se integra melhor com o ecossistema Vue.
Vuex é uma biblioteca de padrões de gerenciamento de estado para aplicativos Vue.js. Ele fornece um store centralizado para todos os componentes da aplicação, com regras garantindo que o estado só possa ser mutado de uma forma previsível.
O uso do Vuex é especialmente recomendado para aplicativos médios a grandes que exigem dados centralizados e reativos acessíveis em diferentes componentes.
Instalação
Via CDN, colocando-o após o vue:
<script src="/caminho/para/vue.js"></script>
<script src="/caminho/para/vuex.js"></script>
Usando NPM:
npm install vuex --save
Usando Yarn:
yarn add vuex
Ao usar um sistema de módulos (como webpack no vue-cli), você precisa instalar explicitamente o Vuex via Vue.use()
:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
Estrutura Básica do Vuex
const store = new Vuex.Store({
state: {},
mutations: {},
actions: {},
getters: {},
});
state
O state
é o objeto que contém todos os dados que podemos acessar a partir dos diferentes componentes. Seu conteúdo só deve ser modificado através de mutations
para que sua modificação seja transparente e auditável.
state: {
count: 0
}
mutations
A única maneira de mudar o estado em um store Vuex é fazendo commit de uma mutation
. Estas funções realizam as modificações e recebem o state
como primeiro parâmetro.
mutations: {
increment (state) {
state.count++
}
}
Mutações não podem ser chamadas diretamente, então elas são executadas com store.commit
:
store.commit('increment');
Mutações também podem receber dados como um segundo parâmetro, que pode ser um número, string, array, etc.
mutations: {
increment (state, amount) {
state.count += amount
}
}
store.commit('increment', 10);
Um ponto importante é que mutações são síncronas, o que significa que quaisquer mudanças de estado devem ser feitas de uma só vez e não através de transações assíncronas como consultas de banco de dados ou API. Para mudanças de estado assíncronas, actions
são usadas.
actions
actions
são semelhantes às mutações, exceto por duas diferenças:
- Elas são despachadas com
store.dispatch
- Em vez de mutar o estado,
actions
fazem commit de mutações actions
podem conter código assíncrono- Elas recebem um objeto
context
como primeiro argumento que nos dá acesso ao state, mutations, actions e getters actions
podem retornar uma promise uma vez resolvida
Suponha que quiséssemos incrementar o contador após consultar alguma API, poderíamos fazer isso assim:
actions: {
asyncIncrement (context) {
return new Promise((resolve, reject) => {
fetch('someApiX').then(() => {
context.commit('increment')
resolve()
})
})
}
}
ou usando async/await
:
actions: {
async asyncIncrement (context) {
await fetch('someApiX')
context.commit('increment')
}
}
e usá-lo assim:
store
.dispatch('asyncIncrement')
.then(() => console.log('Contador incrementado!'));
Saiba mais sobre Promises em JavaScript.
getters
getters
são usados para recuperar informações processadas do state
. Eles recebem o state
como primeiro argumento.
Supondo que tivéssemos uma lista de tarefas no state
, poderíamos criar um getter que retorna apenas as tarefas concluídas:
state: {
tasks: [
{ id: 1, text: 'lorem ipsum', done: true },
{ id: 2, text: 'lorem ipsum', done: true },
{ id: 3, text: 'lorem ipsum', done: false },
{ id: 4, text: 'lorem ipsum', done: false },
{ id: 5, text: 'lorem ipsum', done: true },
]
},
getters: {
doneTasks (state) {
return state.tasks.filter(task => task.done)
}
}
e usá-lo assim:
store.getters.doneTasks;
/*
[
{ id: 1, text: 'lorem ipsum', done: true },
{ id: 2, text: 'lorem ipsum', done: true },
{ id: 5, text: 'lorem ipsum', done: true },
]
*/
getters
também podem receber dados retornando uma função. Então poderíamos recuperar uma tarefa específica por id
através de um getter assim:
getters: {
taskById: (state) => (id) => {
return state.tasks.find(task => task.id === id)
}
}
E usá-lo assim:
store.getters.taskById(2); // { id: 2, text: 'lorem ipsum', done: true }
Dessa forma, podemos gerenciar dados acessíveis em nossos componentes, que são mutáveis por meio de mutações
e ações
e que podemos consultar de forma processada por meio de getters
. Dando à nossa aplicação um padrão padrão de uso de dados que nos permite escalar mais rapidamente.