Подтвердить что ты не робот

Действие Vuex против мутаций

В Vuex, какова логика наличия как "действий", так и "мутаций?"

Я понимаю логику компонентов, которые не могут изменять состояние (что кажется умным), но наличие как действий, так и мутаций кажется, что вы пишете одну функцию для запуска другой функции, чтобы затем изменить состояние.

В чем разница между "действиями" и "мутациями", как они работают вместе, и более того, мне любопытно, почему разработчики Vuex решили сделать это таким образом?

4b9b3361

Ответ 1

Вопрос 1: Почему разработчики Vuejs решили сделать это так?

Ответ:

  1. Когда ваше приложение становится большим, и когда есть несколько разработчиков, работающих над этим проектом, вы обнаружите, что "управление состоянием" (особенно "глобальное состояние") становится все более сложным.
  2. Путь 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

Основные различия между действиями и мутациями:

  1. Внутри действий вы можете запускать асинхронный код, но не в мутациях. Поэтому используйте действия для асинхронного кода, в противном случае используйте мутации.
  2. Внутри действий вы можете получить доступ к получателям, состоянию, мутациям (их фиксация), действиям (отправлять их), в мутациях вы можете получить доступ к состоянию. Поэтому, если вы хотите получить доступ только к состоянию, используйте мутации, в противном случае используйте действия.

Ответ 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 популярными.