Действие Vuex против мутаций
В Vuex, какова логика наличия как "действий", так и "мутаций?"
Я понимаю логику компонентов, которые не могут изменять состояние (что кажется умным), но наличие как действий, так и мутаций кажется, что вы пишете одну функцию для запуска другой функции, чтобы затем изменить состояние.
В чем разница между "действиями" и "мутациями", как они работают вместе, и более того, мне любопытно, почему разработчики Vuex решили сделать это таким образом?
Ответы
Ответ 1
Вопрос 1: Почему разработчики Vuejs решили сделать это так?
Ответ:
- Когда ваше приложение становится большим, и когда есть несколько разработчиков, работающих над этим проектом, вы обнаружите, что "управление состоянием" (особенно "глобальное состояние") становится все более сложным.
- Путь vuex (как и Redux в response.js) предлагает новый механизм управления состоянием, сохранением состояния и "сохранением и отслеживанием" (это означает, что каждое действие, которое изменяет состояние, можно отследить с помощью инструмента отладки: vue-devtools)
Вопрос 2: Какая разница между "действием" и "мутацией"?
Давайте сначала посмотрим на официальное объяснение:
Мутации:
Мутации Vuex - это по существу события: каждая мутация имеет имя и обработчик.
import Vuex from 'vuex'
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
INCREMENT (state) {
// mutate state
state.count++
}
}
})
Действия: Действия - это просто функции, которые отправляют мутации.
// the simplest action
function increment (store) {
store.dispatch('INCREMENT')
}
// a action with additional arguments
// with ES2015 argument destructuring
function incrementBy ({ dispatch }, amount) {
dispatch('INCREMENT', amount)
}
Вот мое объяснение выше:
- мутация - единственный способ изменить состояние
- мутация не заботится о бизнес-логике, она просто заботится о "состоянии",
- действие - бизнес-логика
- действие может отправлять более 1 мутации за раз, оно просто реализует бизнес-логику, оно не заботится об изменении данных (которые управляются мутацией)
Ответ 2
Мутации являются синхронными, тогда как действия могут быть асинхронными.
Иными словами: вам не нужны действия, если ваши операции синхронны, иначе реализуйте их.
Ответ 3
Я считаю, что понимание мотиваций, стоящих за мутациями и действиями, позволяет лучше судить о том, когда использовать, что и как. Это также освобождает программиста от бремени неопределенности в ситуациях, когда "правила" становятся нечеткими. Немного рассуждая об их соответствующих целях, я пришел к выводу, что, хотя могут быть неправильные способы использования действий и мутаций, я не думаю, что существует канонический подход.
Давайте сначала попытаемся понять, почему мы даже совершаем мутации или действия.
Зачем идти по шаблону в первую очередь? Почему бы не изменить состояние непосредственно в компонентах?
Строго говоря, вы можете изменить 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), либо сохранение некоторой обратной совместимости, либо некоторая комбинация всех заявленных возможностей.
Я не верю в то, что передача Геттеров своим Мутациям сама по себе является признаком того, что вы делаете что-то не так. Я считаю это просто "исправлением" одного из недостатков фреймворка.
Ответ 4
Я думаю, что ответ TL;DR заключается в том, что мутации должны быть синхронными/транзакционными. Поэтому, если вам нужно запустить вызов Ajax или сделать любой другой асинхронный код, вам нужно сделать это в Action, а затем зафиксировать мутацию после, чтобы установить новое состояние.
Ответ 5
Отказ от ответственности - я только начал использовать vuejs, так что это только я, экстраполирующий намерение проекта.
Отладка машины времени использует моментальные снимки состояния и показывает временную шкалу действий и мутаций. Теоретически у нас могли бы быть просто actions
наряду с записью штатов и геттеров для синхронного описания мутации. Но потом:
- У нас были бы нечистые исходные данные (асинхронные результаты), которые вызывали сеттеры и геттеры. Это будет сложно логически следовать, и различные асинхронные сеттеры и геттеры могут неожиданно взаимодействовать. Это может произойти с транзакциями с
mutations
но затем мы можем сказать, что транзакция должна быть улучшена, а не быть условием гонки в действиях. Анонимные мутации внутри действия могли бы легче восстановить такие ошибки, поскольку асинхронное программирование является хрупким и сложным. - Журнал транзакций будет трудно читать, потому что не было имени для изменений состояния. Это было бы гораздо более похожим на код и менее английским, без логических группировок мутаций.
- Это может быть более сложным и менее эффективным для записи любой мутации на объект данных, в отличие от того, где теперь есть синхронно определенные точки разлома - до и после вызова функции мутации. Я не уверен, насколько велика проблема.
Сравните следующий журнал транзакций с именованными мутациями.
Action: FetchNewsStories
Mutation: SetFetchingNewsStories
Action: FetchNewsStories [continuation]
Mutation: DoneFetchingNewsStories([...])
С журналом транзакций, который не имеет названных мутаций:
Action: FetchNewsStories
Mutation: state.isFetching = true;
Action: FetchNewsStories [continuation]
Mutation: state.isFetching = false;
Mutation: state.listOfStories = [...]
Надеюсь, вы можете экстраполировать из этого примера потенциальную сложность в асинхронной и анонимной мутации внутри действий.
https://vuex.vuejs.org/en/mutations.html
Теперь представьте, что мы отлаживаем приложение и просматриваем журналы мутаций devtool. Для каждой зарегистрированной мутации devtool нужно будет захватить снимки состояния "раньше" и "после". Тем не менее, асинхронный обратный вызов внутри мутации примера выше делает невозможным: обратный вызов еще не вызывается, когда мутация совершена, и нет никакого способа, чтобы devtool знал, когда вызов будет фактически вызван - любая мутация состояния, выполняемая в обратном вызове по существу не отслеживается!
Ответ 6
Согласно docs
Действия аналогичны мутациям, причем различия заключаются в следующем:
- Вместо того, чтобы мутировать состояние, действия совершают мутации.
- Действия могут содержать произвольные асинхронные операции.
Рассмотрим следующий фрагмент.
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++ //Mutating the state. Must be synchronous
}
},
actions: {
increment (context) {
context.commit('increment') //Committing the mutations. Can be asynchronous.
}
}
})
Обработчики действий (increment) получают объект контекста, который предоставляет тот же набор методов/свойств экземпляра хранилища, поэтому вы можете вызвать context.commit для фиксации мутации или доступа к состоянию и геттерам через context.state и context.getters
Ответ 7
Основные различия между действиями и мутациями:
- Внутри действий вы можете запускать асинхронный код, но не в мутациях. Поэтому используйте действия для асинхронного кода, в противном случае используйте мутации.
- Внутри действий вы можете получить доступ к получателям, состоянию, мутациям (их фиксация), действиям (отправлять их), в мутациях вы можете получить доступ к состоянию. Поэтому, если вы хотите получить доступ только к состоянию, используйте мутации, в противном случае используйте действия.
Ответ 8
Это тоже смутило меня, поэтому я сделал простую демонстрацию.
component.vue
<template>
<div id="app">
<h6>Logging with Action vs Mutation</h6>
<p>{{count}}</p>
<p>
<button @click="mutateCountWithAsyncDelay()">Mutate Count directly with delay</button>
</p>
<p>
<button @click="updateCountViaAsyncAction()">Update Count via action, but with delay</button>
</p>
<p>Note that when the mutation handles the asynchronous action, the "log" in console is broken.</p>
<p>When mutations are separated to only update data while the action handles the asynchronous business
logic, the log works the log works</p>
</div>
</template>
<script>
export default {
name: 'app',
methods: {
//WRONG
mutateCountWithAsyncDelay(){
this.$store.commit('mutateCountWithAsyncDelay');
},
//RIGHT
updateCountViaAsyncAction(){
this.$store.dispatch('updateCountAsync')
}
},
computed: {
count: function(){
return this.$store.state.count;
},
}
}
</script>
store.js
import 'es6-promise/auto'
import Vuex from 'vuex'
import Vue from 'vue';
Vue.use(Vuex);
const myStore = new Vuex.Store({
state: {
count: 0,
},
mutations: {
//The WRONG way
mutateCountWithAsyncDelay (state) {
var log1;
var log2;
//Capture Before Value
log1 = state.count;
//Simulate delay from a fetch or something
setTimeout(() => {
state.count++
}, 1000);
//Capture After Value
log2 = state.count;
//Async in mutation screws up the log
console.log('Starting Count: ${log1}'); //NRHG
console.log('Ending Count: ${log2}'); //NRHG
},
//The RIGHT way
mutateCount (state) {
var log1;
var log2;
//Capture Before Value
log1 = state.count;
//Mutation does nothing but update data
state.count++;
//Capture After Value
log2 = state.count;
//Changes logged correctly
console.log('Starting Count: ${log1}'); //NRHG
console.log('Ending Count: ${log2}'); //NRHG
}
},
actions: {
//This action performs its async work then commits the RIGHT mutation
updateCountAsync(context){
setTimeout(() => {
context.commit('mutateCount');
}, 1000);
}
},
});
export default myStore;
Изучив это, я пришел к выводу, что мутации - это конвенция, ориентированная только на изменение данных, чтобы улучшить отдельные проблемы и улучшить регистрацию до и после обновленных данных. В то время как действия представляют собой уровень абстракции, который обрабатывает логику более высокого уровня и затем соответствующим образом вызывает мутации
Ответ 9
Мутации:
Can update the state. (Having the Authorization to change the state).
Действия:
Actions are used to tell "which mutation should be triggered"
В Redux Way
Mutations are Reducers
Actions are Actions
Почему Оба?
Когда приложение растет, кодирование и линии будут увеличиваться, тогда вам придется обрабатывать логику в действиях, а не в мутациях, потому что мутации являются единственным полномочием изменять состояние, оно должно быть чистым, насколько это возможно.
Ответ 10
1. Из документов:
Действия аналогичны мутациям, причем различия заключаются в следующем:
- Вместо того, чтобы мутировать состояние, действия совершают мутации.
- Действия могут содержать произвольные асинхронные операции.
Действия могут содержать асинхронные операции, но мутация не может.
2. Мы вызываем мутацию, мы можем напрямую изменить состояние. и мы также можем в действии изменить состояния следующим образом:
actions: {
increment (store) {
// do whatever ... then change the state
store.dispatch('MUTATION_NAME')
}
}
Действия предназначены для обработки других вещей, мы можем делать много вещей там (мы можем использовать асинхронные операции), а затем изменять состояние путем мутаций отправки там.
Ответ 11
Потому что нет состояния без мутаций! При совершении - выполняется часть логики, которая изменяет состояние в обозримом режиме. Мутации - это единственный способ установить или изменить состояние (поэтому нет прямых изменений!), И, более того, они должны быть синхронными. Это решение управляет очень важной функциональностью: мутации регистрируются в devtools. И это обеспечивает отличную читаемость и предсказуемость!
Еще одна вещь - действия. Как было сказано, действия совершают мутации. Поэтому они не меняют магазин, и им не нужно синхронно. Но они могут управлять дополнительной частью асинхронной логики!
Ответ 12
Может показаться ненужным иметь дополнительный уровень actions
только для вызова mutations
, например:
const actions = {
logout: ({ commit }) => {
commit("setToken", null);
}
};
const mutations = {
setToken: (state, token) => {
state.token = token;
}
};
Итак, если вызывающие actions
вызывают logout
, почему бы не вызвать саму мутацию?
Вся идея действия заключается в том, чтобы вызвать несколько мутаций внутри одного действия или сделать Ajax-запрос или любую другую асинхронную логику, которую вы можете себе представить.
Мы можем в конечном итоге иметь действия, которые делают несколько сетевых запросов и в конечном итоге вызывают много разных мутаций.
Поэтому мы стараемся максимально использовать сложность нашего Vuex.Store()
в наших actions
и это делает наши mutations
, state
и getters
более чистыми и прямыми и соответствует типу модульности, который делает такие библиотеки, как Vue и React популярными.