Acredito que ter uma compreensão das motivações por trás de Mutações e ações permite julgar melhor quando usar qual e como. Também libera o programador do fardo da incerteza em situações em que as "regras" se tornam imprecisas. Depois de pensar um pouco sobre seus respectivos propósitos, cheguei à conclusão de que, embora possa haver formas erradas de usar Ações e Mutações, não acho que exista uma abordagem canônica.
Vamos primeiro tentar entender por que passamos por mutações ou ações.
Por que passar pelo clichê em primeiro lugar? Por que não mudar de estado diretamente nos componentes?
A rigor, você pode alterar state
diretamente os componentes. O state
é apenas um objeto JavaScript e não há nada mágico que reverta as alterações feitas nele.
// Yes, you can!
this.$store.state['products'].push(product)
No entanto, ao fazer isso, você está espalhando suas mutações de estado por todo o lugar. Você perde a capacidade de simplesmente abrir um único módulo que hospeda o estado e ver rapidamente que tipo de operações pode ser aplicada a ele. A existência de mutações centralizadas resolve isso, embora ao custo de alguns clichês.
// so we go from this
this.$store.state['products'].push(product)
// to this
this.$store.commit('addProduct', {product})
...
// and in store
addProduct(state, {product}){
state.products.push(product)
}
...
Eu acho que se você substituir algo curto pelo clichê, você desejará que o clichê também seja pequeno. Portanto, presumo que as mutações devem ser invólucros muito finos em torno das operações nativas no estado, com quase nenhuma lógica de negócios. Em outras palavras, as mutações devem ser usadas principalmente como criadores.
Agora que você centralizou suas mutações, você tem uma melhor visão geral de suas alterações de estado e, como suas ferramentas (vue-devtools) também conhecem esse local, facilita a depuração. Também é importante ter em mente que muitos plugins da Vuex não assistem diretamente ao estado para rastrear as mudanças, eles confiam em mutações para isso. Alterações "fora do limite" no estado são, portanto, invisíveis para elas.
Então mutations
, actions
qual é a diferença, afinal?
Ações, como mutações, também residem no módulo da loja e podem receber o state
objeto. O que implica que eles também poderiam sofrer uma mutação direta. Então, qual é o sentido de ter os dois? Se argumentamos que as mutações precisam ser pequenas e simples, isso implica que precisamos de um meio alternativo para abrigar uma lógica de negócios mais elaborada. Ações são os meios para fazer isso. E, como estabelecemos anteriormente, o vue-devtools e os plugins estão cientes das mudanças por meio do Mutations. Para manter a consistência, devemos continuar usando o Mutations em nossas ações. Além disso, como as ações devem ser abrangentes e que a lógica que elas encapsulam pode ser assíncrona, faz sentido que as ações também sejam simplesmente assíncronas desde o início.
Muitas vezes, é enfatizado que as ações podem ser assíncronas, enquanto as mutações normalmente não são. Você pode decidir ver a distinção como uma indicação de que mutações devem ser usadas para qualquer coisa síncrona (e ações para qualquer coisa assíncrona); no entanto, você terá algumas dificuldades se, por exemplo, precisar cometer mais de uma mutação (de forma síncrona) ou se precisar trabalhar com um Getter a partir de suas mutações, pois as funções de mutação não recebem Getters nem Mutações como argumentos ...
... o que leva a uma pergunta interessante.
Por que as mutações não recebem Getters?
Ainda não encontrei uma resposta satisfatória para esta pergunta. Eu vi algumas explicações da equipe principal que achei discutíveis na melhor das hipóteses. Se eu resumir seu uso, os Getters devem ser extensões computadas (e geralmente armazenadas em cache) para o estado. Em outras palavras, eles ainda são basicamente o estado, embora exijam alguma computação inicial e normalmente são somente leitura. É assim que são incentivados a serem usados.
Portanto, impedir que o Mutations acesse diretamente o Getters significa que agora é necessário um de três itens, se precisarmos acessar do antigo alguma funcionalidade oferecida pelo último: (1) os cálculos de estado fornecidos pelo Getter são duplicados em algum lugar acessível para a Mutação (mau cheiro) ou (2) o valor calculado (ou o próprio Getter relevante) é passado como um argumento explícito para a Mutação (descolada), ou (3) a lógica do Getter é duplicada diretamente dentro da Mutação , sem o benefício adicional do armazenamento em cache, conforme fornecido pelo Getter (fedor).
A seguir, é apresentado um exemplo de (2), que na maioria dos cenários que encontrei parece a opção "menos ruim".
state:{
shoppingCart: {
products: []
}
},
getters:{
hasProduct(state){
return function(product) { ... }
}
}
actions: {
addProduct({state, getters, commit, dispatch}, {product}){
// all kinds of business logic goes here
// then pull out some computed state
const hasProduct = getters.hasProduct(product)
// and pass it to the mutation
commit('addProduct', {product, hasProduct})
}
}
mutations: {
addProduct(state, {product, hasProduct}){
if (hasProduct){
// mutate the state one way
} else {
// mutate the state another way
}
}
}
Para mim, o exposto acima parece não apenas um pouco complicado, mas também um pouco "vazante", uma vez que parte do código presente na Ação está claramente escorrendo da lógica interna da Mutação.
Na minha opinião, isso é uma indicação de um compromisso. Acredito que permitir que o Mutations receba automaticamente Getters apresenta alguns desafios. Pode ser tanto para o design do próprio Vuex, como para o ferramental (vue-devtools et al), ou para manter alguma compatibilidade com versões anteriores ou uma combinação de todas as possibilidades declaradas.
O que eu não acredito é que passar Getters para suas Mutações é necessariamente um sinal de que você está fazendo algo errado. Eu vejo isso simplesmente como "corrigir" uma das falhas do framework.