A única maneira de mudar o estado em um store Vuex é fazendo commit de uma mutação. As mutações Vuex são muito semelhantes aos eventos: cada mutação possui uma String tipo e um manipulador. A função do manipulador é onde nós executamos modificações de estado reais e receberá o estado como o 1º argumento:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// muda o estado
state.count++
}
}
})
Você não pode chamar diretamente um manipulador de mutação. Pense nisso mais como registro de eventos: "Quando uma mutação com tipo incremento
é ativada, chame esse manipulador." Para invocar um manipulador de mutação, você precisa chamar store.commit
com seu tipo:
store.commit('increment')
Você pode passar um argumento adicional para store.commit
, que é chamado de payload para a mutação:
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
Na maioria dos casos, o payload deve ser um objeto para que ele possa conter vários campos e a mutação gravada também será mais descritiva:
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
Uma maneira alternativa de confirmar uma mutação é usando diretamente um objeto que possui uma propriedade type
:
store.commit({
type: 'increment',
amount: 10
})
Ao fazer um commit com object-style, todo o objeto será passado como payload para os manipuladores de mutação, de modo que o manipulador permaneça o mesmo:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
Uma vez que o estado de um store Vuex é reativo para o Vue, quando mutar o estado, os componentes Vue que observam o estado serão atualizados automaticamente. Isso também significa que as mutações Vuex estão sujeitas às mesmas ressalvas de reatividade quando se trabalha com apenas Vue:
Prefira inicializar o estado inicial do seu store com todos os campos desejados antecipadamente.
Ao adicionar novas propriedades a um objeto, você deve:
Use Vue.set(obj, 'newProp', 123)
, ou
Substitua esse Objeto por um novo. Por exemplo, usando o estágio 3 object spread syntax, podemos gravá-lo assim:
state.obj = { ...state.obj, newProp: 123 }
É um padrão normalmente visto para usar constantes para tipos de mutação em várias implementações de Flux. Isso permite que o código aproveite ferramentas como linters e colocar todas as constantes em um único arquivo permite que seus colaboradores tenham uma visão rápida de quais mutações são possíveis em todo o aplicativo:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// podemos usar o recurso de dado computado do ES2015
// usar uma constante como o nome da função
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Querer usar constantes é ,em grande parte, uma preferência - pode ser útil em grandes projetos com muitos desenvolvedores, mas é totalmente opcional se você não gosta deles.
Uma regra importante a lembrar é que as funções do manipulador de mutação devem ser síncronas. Por quê? Considere o seguinte exemplo:
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
Agora imagine que estamos depurando o aplicativo e observamos os registros de mutação do devtool. Para cada mutação registrada, o devtool precisará capturar "antes" e "depois" snapshots do estado. No entanto, o retorno de chamada assíncrono dentro da mutação de exemplo acima torna isso impossível: o callback ainda não foi chamado quando a mutação foi comitada, e não existe nenhuma maneira para o devtool saber quando o callback será chamado - qualquer mutação de estado realizada no retorno desse callback é essencialmente não rastreável!
Você pode fazer um commit de mutações em componentes com this.$store.commit('xxx')
, ou use o auxiliar mapMutations
que mapeia métodos de componente para chamadas store.commit
(requer injeção do store na instância raiz):
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // mapeia `this.increment()` para `this.$store.commit('increment')`
// `mapMutations` também suporta payloads:
'incrementBy' // mapeia `this.incrementBy(amount)` para `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // mapeia `this.add()` para `this.$store.commit('increment')`
})
}
}
Assincronicidade combinada com a mutação do estado pode tornar o seu programa muito difícil de acontecer. Por exemplo, quando você chama dois métodos com retrocessos assíncronos que mutam o estado, como você sabe quando eles são chamados e qual callback foi chamado primeiro? É exatamente por isso que queremos separar os dois conceitos. No Vuex, as mutações são transações síncronas:
store.commit('increment')
// qualquer alteração de estado que a mutação "increment" possa causar
// deve ser feita neste momento
Para lidar com operações assíncronas, vamos apresentar as Ações.