Vuex
O padrão de gerenciamento de estado do vue
O Vuex é um Padrão e ao mesmo tempo uma Biblioteca para Gerenciamento de Estado Centralizado em aplicação Vue. Ele especifica regras que garantem que o Estado somente possa ser alterado (mutado) de uma forma previsível.
Gerenciamento de Estado
- Valor do estado em um local único.
Gerenciamento Global
- Qualquer componente pode ter acesso e modificar o estado.
Instância Vue e Vuex Store
Para entender melhor como podemos integrar o vuex em nossa aplicação Vue e simplificar um pouco no entendimento de seus conceitos, vamos fazer um comparativo do gerenciamento de estado de ambos os casos, tanto o do vue como o do vuex!
A Store do Vuex é o ponto central onde será armazenado os dados da aplicação.
Nós sabemos que na instancia Vue nós criamos o new Vue e passamos um objeto de configuração, a mesma coisa você fará com o Vuex Store! Cria uma nova instancia pelo Vuex Store e passa também um objeto de configurações.
Na instancia Vue nós temos a propriedade data,que é onde armazenamos o estado da nossa aplicação, ela é a nossa fonte de verdade. Já no Vuex nós temos o state, que é a mesma coisa que o data no Vue, ou seja, o estado centralizado da aplicação.
No Vue também temos os methods. Eles podem alterar o estado da nossa aplicação, já no Vuex nós temos as actions, que vai alterar o state da nossa store.
No Vue nós temos as computed, no qual podemos acessar dados da nossa aplicação, transformando eles, enfim. As computed dependem da propriedade a qual elas acessam no objeto data e só serão reavaliadas quando a propriedade for alterada, pois trabalha como uma forma de cache desses valores.
Já no Vuex temos algo semelhante que chamamos de getters, eles podem ser utilizados pra acessar os dados do state e vão passar a depender da propriedade. Quando alguma propriedade do meu state for alterada, o meu getter é executado. :)
A unica diferença é nas mutations. Elas nos permitem causar alterações ou mutações na nossa store do vuex de forma previsível, a melhor pratica é que as mutations façam as alterações diretas no state, e ai dessa forma conseguimos rastrear essas alterações. São as nossas actions que irão chamar as nossas mutations.
Visão geral do Vuex
No Estado Central da aplicação temos os componentes do Vue. Eles podem estar aninhados a qualquer nível. Nós teremos as actions que vão executar(commit) as mutations que, por sua vez, vão alterar o state.
Os componentes vão disparar(dispatch) as ações(actions), e as ações commitam as mutations, ou seja, confirmam essas ações e chamam a mutation. A mutation vai receber os dados e causar uma mudança no state dai então meu componente será renderizado novamente.
O state pode ser acessado diretamente, ou então por meio dos getters. Eu posso criar um getter pra realizar uma filtragem, por exemplo, no qual será retornado somente os dados do usuário do sexo feminino.
As mutations precisam rodar de forma síncrona, já as actions não. Então, como uma chamada pro servidor é assíncrona, eu posso fazer dentro das actions essa chamada pra API, o servidor vai montar a resposta e quando tiver pronto ele devolve pra aplicação, e na action nós vamos ter um callback aguardando a resposta, no qual dentro dele extraímos os dados, daí quando a resposta chegar eu commito a mutation, a mutation pega a lista que ta chegando da action pra ela mesma e joga no state, o state sofre mudanças e então o componente é renderizado.
Por meio do vue devtools podemos inspecionar todo estado da nossa aplicação! :)
Configuração do Vuex
No exemplo abaixo, foi criado um arquivo store.js que ficará salvo o meu gerenciamento global com o state, mutations, actions, etc. Depois de dizer para o Vue que ele usará o Vuex, eu vou exportar um novo arquivo Vuex.
O primeiro item criado foi o state com a propriedade user que tem uma string de valor Mandy. Agora eu vou poder ter acesso a essa informação em TODOS os meus componentes do vue :)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: "Mandy"
},
})E no main.js eu faço a importação do meu store.js e adiciono para o Vue e ai ele ja vai está sendo utilizado.
import store from './store.js'
new Vue({
store,
render: h => h(App)
}).$mount('#app')STORE
Teremos acesso ao objeto $store em qualquer componente. Assim, podemos acessar por exemplo o state de um item utilizando $store.state.user.
<!-- User.vue -->
<template>
<p>{{ $store.state.user }}</p>
</template>
<script>
export default {
name: "Aulas",
created() {
console.log(this.$store.state.user);
}
};
</script>MapState
Com a função mapState você consegue criar um atalho sem a necessidade de criar um por um, basta passar um array e dar o nome de cada state que deseja do $store.state.
<!-- User.vue -->
<template>
<p>{{ id }}: {{ user }}</p>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Aulas",
computed: ...mapState(["user", "id"]),
};
</script>Entenda o que aconteceu no código acima:
// o uso do mapState retorna um objeto:
mapState(["user", "id"]);
// retorna o equivalente a isto:
{
user() {
return this.$store.state.user;
},
id() {
return this.$store.state.id;
}
}
/* Por isso podemos utilizar o operador spread para espalhar
esse objeto dentro da computed */
import { mapState } from "vuex";
export default {
name: "Aulas",
computed: {
...mapState(["user", "id"]),
}
};Mutations
Devemos sempre utilizar mutações para modificarmos um state(estado). Pense em mutations como se fossem métodos (methods).
// store.js
export default new Vuex.Store({
state: {
user: "Mandy",
},
mutations: {
changeUser(state) {
state.user = "Leonardo Da Vinci"
}
}
})Fazendo uso da mutation no arquivo Vue:
<!-- Button.vue -->
<template>
<div>
<button @click="handleClick">Clique Aqui para alterar o nome do Usuário</button>
<p>{{ user }}</p>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Button",
methods: {
handleClick() {
// o commit é utilizado para ativar a mutação.
this.$store.commit("changeUser");
}
},
computed: {
...mapState(["user"])
}
};
</script>O código dentro de mutações deve ser síncrono (não utilizar fetch ou outros métodos assíncronos). Para estes devemos utilizar actions...
Payload
É chamado de Payload o valor que pode ser enviado com uma commit para uma mutation.
// store.js
export default new Vuex.Store({
state: {
user: "Mandy",
},
mutations: {
changeUser(state, payload) {
state.user = payload
}
}
})Uso no arquivo Vue:
<script>
export default {
name: "Button",
methods: {
handleClick() {
// O segundo argumento é o payload
this.$store.commit("changeUser", "Van Gogh");
}
},
computed: {
...mapState(["user"])
}
};
</script>Geralmente enviamos um payload como um objeto, assim temos a possibilidade de enviarmos diferentes dados para as mutações.
// store.js
export default new Vuex.Store({
state: {
user: {
name: "Mandy",
id: "1234"
}
},
mutations: {
changeUser(state, payload) {
state.user.name = payload.name
state.user.id = payload.id
}
}
})Quando a função handleClick() for ativada, o meu objeto user no estado do Vuex será alterado para os novos dados passados.
<script>
export default {
methods: {
handleClick() {
this.$store.commit("changeUser", {
name: "Van Gogh",
id: "3321"
});
}
},
};
</script>mapMutations
Também temos o mapMutations para facilitar o uso de mutações, podemos incluir as mesmas dentro dos métodos (methods). Veja como esta alteração deixa o código mais simples.
<script>
import { mapState, mapMutations } from "vuex";
export default {
name: "Button",
methods: {
...mapMutations(["changeUser"]),
handleClick() {
// agora temos acesso ao método this.changeUser
this.changeUser({
name: "Van Gogh",
id: "3321"
});
}
},
computed: {
...mapState(["user"])
}
};
</script>ALL_CAPS
É comum utilizarmos ALL_CAPS para definirmos o nome de uma mutação.
// store.js
export default new Vuex.Store({
state: {
name: "Mandy",
},
mutations: {
CHANGE_USER(state, payload) {
state.name = payload.name
}
}
})Actions
Quando precisamos de uma lógica ligada a alteração do state, ou caso exista a necessidade de dispararmos mais de uma mutação, utilizamos actions. As actions também permitem eventos assíncronos, diferente das mutations.
// store.js
export default new Vuex.Store({
state: {
name: "Mandy",
id: "2345",
},
mutations: {
CHANGE_USER(state, payload) {
state.name = payload.name
},
CHANGE_ID(state, payload) {
state.id = payload.id
}
},
actions: {
changeUser(context, payload) {
context.commit("CHANGE_USER", payload);
context.commit("CHANGE_ID", payload);
}
}
})Veja abaixo o uso em um arquivo Vue:
<template>
<div>
<button @click="handleClick">Clique Aqui para alterar o nome do Usuário</button>
<p>{{ user }}</p>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Button",
methods: {
handleClick() {
// o dispatch é utilizado para ativar a action.
this.$store.dispatch("changeUser", {
name: "Van Gogh",
id: "2345"
});
}
},
computed: {
...mapState(["name", "id"])
}
};
</script>mapActions
Também podemos utilizar o mapActions para facilitar o uso dentro de componentes.
<script>
import { mapState, mapActions } from "vuex";
export default {
name: "Button",
methods: {
...mapActions(["changeUser"]),
handleClick() {
this.changeUser({
name: "Van Gogh",
id: "3321"
});
}
},
computed: {
...mapState(["name", "id"])
}
};
</script>Actions podem ser utilizadas para executar código assíncrono (fetch).
// store.js
export default new Vuex.Store({
state: {
acao: null,
},
mutations: {
UPDATE_ACAO(state, payload) {
state.acao = payload
},
},
actions: {
fetchAcao(context) {
fetch("https://api.iextrading.com/1.0/stock/aapl/quote")
.then(r => r.json())
.then(r => {
context.commit("UPDATE_ACAO", r)
})
}
}
})<template>
<div>
<button @click="fetchAcao">Clique aqui para fazer o fetch</button>
<p>{{ acao }}</p>
</div>
</template>
<script>
import { mapState, mapActions } from "vuex";
export default {
name: "Acoes",
methods: {
...mapActions(["fetchAcao"])
},
computed: {
...mapState(["acao"])
}
};
</script>Getters e argumentos
Podemos passar argumentos para os getters. Para isso precisamos retornar uma função.
<script>
state: {
getters: {
livrosLidos(state) {
return function(lido) {
return state.livros.filter(livro => livro.lido === lido)
}
},
// ou
// livrosLidos: state => lido => state.livros.filter(livro => livro.lido === lido)
}
}
</script><template>
<div>
<p>{{ livrosLidos(false) }}</p>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Livros",
computed: {
...mapGetters(["livrosLidos"])
}
};
</script>Modules
Quando seu aplicativo começa a crescer e ficar mais complexo, o ideal é separar o código em diferentes partes. Para isso podemos utilizar os modules.
// store/index.js
import login from '@/store/login.js'
import cart from '@/store/cart.js'
export default new Vuex.Store({
modules: {
login,
cart
},
state: {
logo: "Tech"
}
})// store/cart.js
export default {
state: {
carrinho: []
},
mutations: {
ADD_CART(state, payload) {
state.carrinho.push(payload)
}
}
}Usando no componente Vue:
<template>
<div>
<p>{{ $store.state.cart.carrinho }}</p>
<p>{{ $store.state.logo }}</p>
</div>
</template>Namespaced
Apesar do state ficar separado por módulo, os getters, mutations e actions não ficam separados automaticamente. Para isso precisamos indicar no módulo utilizando a propriedade namespaced.
// store/cart.js
export default {
namespaced: true,
state: {
carrinho: []
},
mutations: {
ADD_CART(state, payload) {
state.carrinho.push(payload)
}
}
}<script>
export default {
methods: {
handleClick() {
/* Agora é necessário adicionar o nome registrado em modules
para ativar a mutação dentro de cart.js. */
this.$store.commit("cart/ADD_CART");
}
}
}
</script>Espero que com estas explicações você consiga entender melhor o uso do Vuex e consiga brincar um pouco com ele nos seus projetinhos!
Até logo! (◠‿◕✿)
Comentários
Prefere comentar em ânonimo? Siga os seguintes passos:
- Clique no campo "Nome"
- Marque os itens necessários, principalmente o último: "Prefiro publicar como um visitante"
- Adicione um email
- Agora só enviar seu comentário :)