Мутации
Единственным способом изменения состояния хранилища во Vuex являются мутации. Мутации во Vuex очень похожи на события: каждая мутация имеет строковый тип и функцию-обработчик. В этом обработчике и происходят, собственно, изменения состояния, переданного в функцию первым аргументом:
const store = createStore({
state: {
count: 1
},
mutations: {
increment(state) {
// изменяем состояние
state.count++
}
}
})
Вызывать функцию-обработчик напрямую — нельзя. Это больше похоже на обработку события: «Когда мутация типа increment
инициирована, вызывается этот обработчик». Чтобы инициировать обработку мутации, необходимо вызвать store.commit
, указав её тип:
store.commit('increment')
Мутации с нагрузкой
При вызове store.commit
в мутацию можно также передать дополнительный параметр, называемый нагрузкой (payload
):
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
В большинстве случаев нагрузка будет объектом, содержащим несколько полей. Запись мутаций в таком случае становится более описательной:
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
Объектный синтаксис
Другой способ вызвать мутацию — это передать в commit единственный параметр, в котором type
указан напрямую:
store.commit({
type: 'increment',
amount: 10
})
При использовании объектной записи, объект передаётся в качестве нагрузки целиком, так что обработчик остаётся тем же самым:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
Использование констант для обозначения типов мутаций
В разных вариантах реализации Flux такой подход применяется весьма часто. Вынесение всех констант с типами мутаций и действий в отдельный файл позволяет упростить использования линтеров и других инструментов, а также чтобы дать другим разработчикам возможность с первого взгляда понять, какие мутации возможны в приложении:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import { createStore } from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = createStore({
state: { ... },
mutations: {
// «вычисляемые имена» из ES2015 позволяют использовать
// константу в качестве имени функции
[SOME_MUTATION] (state) {
// здесь будет изменяться состояние
}
}
})
Тем не менее, использовать константы для указания типов мутаций совершенно необязательно, хотя это и может оказаться полезным в крупных проектах.
Мутации должны быть синхронными
Нужно помнить одно важное правило: обработчики мутаций обязаны быть синхронными. Почему? Рассмотрим пример:
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
Теперь представьте, что отлаживаете приложение и смотрите в лог мутаций в инструментах разработчика. Для каждой залогированной мутации devtools должен сохранить слепки состояния приложения «до» и «после» её наступления. Однако, асинхронный коллбэк внутри приведённой выше мутации делает это невозможным: мутация-то уже записана, и у devtools нет никакой возможности знать, что далее будет вызван коллбэк, а, значит, и инициируемые им изменения становится, по сути дела, невозможно отследить.
Вызов мутаций в компонентах
Мутации можно вызывать из кода компонентов, используя this.$store.commit('xxx')
, или применяя вспомогательный метод mapMutations
, который проксирует вызовы store.commit
через методы компонентов (для этого требуется наличие корневой ссылки на хранилище $store
):
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // `this.increment()` будет вызывать `this.$store.commit('increment')`
// mapMutations также поддерживает нагрузку:
'incrementBy' // `this.incrementBy(amount)` будет вызывать `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // `this.add()` будет вызывать `this.$store.commit('increment')`
})
}
}
О действиях
Привнесение асинхронности в мутации могло бы изрядно затруднить понимание логики программы. Например, если вызываются два метода, оба с асинхронными коллбэками, изменяющими состояние приложения — как предсказать, какой из коллбэков будет вызван первым? Именно поэтому концепции изменений и асинхронности рассматриваются по отдельности. Во Vuex мутации — это синхронные транзакции:
store.commit('increment')
// все изменения состояния, вызываемые мутацией «increment»,
// к этому моменту уже должны произойти.
Для обработки асинхронных операций существуют действия.