Я считаю, что понимание мотиваций, стоящих за мутациями и действиями, позволяет лучше судить о том, когда использовать, что и как. Это также освобождает программиста от бремени неопределенности в ситуациях, когда «правила» становятся нечеткими. Немного рассуждая об их соответствующих целях, я пришел к выводу, что, хотя могут определенно быть неправильные способы использования действий и мутаций, я не думаю, что существует канонический подход.
Давайте сначала попробуем понять, почему мы даже проходим либо Мутации, либо Действия.
Зачем идти по шаблону в первую очередь? Почему бы не изменить состояние непосредственно в компонентах?
Строго говоря, вы можете изменить state
непосредственно из ваших компонентов. Это state
просто объект JavaScript, и нет ничего волшебного, что могло бы отменить изменения, которые вы внесли в него.
// Yes, you can!
this.$store.state['products'].push(product)
Однако, делая это, вы разбрасываете мутации своего состояния повсюду. Вы теряете возможность просто открыть единственный модуль, в котором находится состояние, и сразу увидеть, какие операции могут быть применены к нему. Централизованные мутации решают это, хотя и за счет некоторого шаблона.
// 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)
}
...
Я думаю, что если вы замените что-то короткое на шаблон, вы захотите, чтобы шаблон также был маленьким. Поэтому я предполагаю, что мутации должны быть очень тонкими обертками вокруг нативных операций над состоянием, почти без бизнес-логики. Другими словами, мутации предназначены для использования в основном как сеттеры.
Теперь, когда вы централизовали свои мутации, у вас есть лучший обзор изменений вашего состояния, и поскольку ваш инструмент (vue-devtools) также знает об этом месте, это облегчает отладку. Также стоит помнить, что многие плагины Vuex не отслеживают состояние напрямую, чтобы отслеживать изменения, а скорее полагаются на мутации. Таким образом, изменения состояния невидимы для них.
Итак mutations
, в actions
чем разница в любом случае?
Действия, такие как мутации, также находятся в модуле магазина и могут получать state
объект. Что подразумевает, что они могут также изменить его напрямую. Так какой смысл иметь оба? Если мы рассуждаем о том, что мутации должны быть небольшими и простыми, это означает, что нам нужны альтернативные средства для размещения более сложной бизнес-логики. Действия являются средством для этого. И поскольку, как мы установили ранее, vue-devtools и плагины знают об изменениях посредством мутаций, чтобы оставаться последовательными, мы должны продолжать использовать мутации из наших действий. Кроме того, поскольку все действия должны охватывать все объекты, а логика, которую они инкапсулируют, может быть асинхронной, имеет смысл, что действия с самого начала просто делались бы асинхронными.
Часто подчеркивается, что действия могут быть асинхронными, а мутации - нет. Вы можете решить рассматривать различие как указание на то, что мутации должны использоваться для чего-то синхронного (и действия для чего-либо асинхронного); тем не менее, вы столкнетесь с некоторыми трудностями, если, например, вам нужно будет зафиксировать более одной мутации (синхронно) или вам нужно работать с геттером из ваших мутаций, поскольку функции мутации не получают ни геттеры, ни мутации в качестве аргументов ...
... что приводит к интересному вопросу.
Почему мутации не получают геттеры?
Я еще не нашел удовлетворительного ответа на этот вопрос. Я видел некоторые объяснения со стороны основной команды, что я нашел спор в лучшем случае. Если я резюмирую их использование, то получатели должны быть вычислены (и часто кэшированы) расширениями состояния. Другими словами, они в основном все еще находятся в состоянии, хотя и требуют предварительных вычислений и обычно доступны только для чтения. По крайней мере, так их поощряют.
Таким образом, предотвращение прямого доступа мутаций к геттерам означает, что теперь необходима одна из трех вещей, если нам необходимо получить доступ от первого к некоторым функциональным возможностям, предлагаемым последним: (1) либо вычисления состояний, предоставляемые геттером, дублируются где-то, что доступно в Мутацию (неприятный запах), или (2) вычисленное значение (или соответствующий сам Геттер) передается в качестве явного аргумента Мутации (фанки), или (3) сама логика Геттера дублируется непосредственно в Мутации без дополнительного преимущества кэширования, предоставляемого Getter (stench).
Ниже приведен пример (2), который в большинстве сценариев, с которыми я столкнулся, кажется «наименее плохим» вариантом.
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
}
}
}
На мой взгляд, вышесказанное кажется не только немного запутанным, но и несколько «утечкой», поскольку часть кода, присутствующего в Action, явно вытекает из внутренней логики Mutation.
На мой взгляд, это показатель компромисса. Я считаю, что разрешение мутациям автоматически получать геттеров сопряжено с некоторыми трудностями. Это может быть либо дизайн самого Vuex, либо инструментарий (vue-devtools et al), либо сохранение некоторой обратной совместимости, либо некоторая комбинация всех заявленных возможностей.
Я не верю в то, что передача Геттеров своим Мутациям сама по себе является признаком того, что вы делаете что-то не так. Я считаю это просто «исправлением» одного из недостатков фреймворка.