2024-10-01 09:00:04

Vuex行动vs突变

在Vuex中,同时拥有“动作”和“突变”的逻辑是什么?

我理解组件不能修改状态的逻辑(这看起来很聪明),但同时拥有动作和突变似乎是在编写一个函数来触发另一个函数,然后再改变状态。

“动作”和“突变”之间的区别是什么,它们是如何一起工作的,更重要的是,我很好奇Vuex开发人员为什么决定这样做?


当前回答

因为没有突变就没有状态!提交时——执行以可预见的方式改变状态的一段逻辑。突变是设置或改变状态的唯一方法(所以没有直接的变化!),而且它们必须是同步的。这个解决方案驱动了一个非常重要的功能:突变将登录到devtools。这为您提供了良好的可读性和可预测性!

还有一件事——行动。正如我们所说的,行为会导致突变。所以它们不会改变存储,也不需要这些是同步的。但是,他们可以管理一个额外的异步逻辑!

其他回答

这也让我感到困惑,所以我做了一个简单的演示。

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;

经过研究,我得出的结论是,突变是一种只关注于更改数据以更好地分离关注点和改进更新数据前后的日志记录的约定。而动作是一个抽象层,它处理更高层次的逻辑,然后适当地调用突变

1.从文档:

动作类似于突变,区别在于:

操作不会导致状态突变,而是导致突变。 动作可以包含任意异步操作。

action可以包含异步操作,但是突变不能。

2.我们调用突变,就能直接改变状态。我们也可以这样改变状态:

actions: {
  increment (store) {
    // do whatever ... then change the state
    store.commit('MUTATION_NAME')
  }
}

action是为处理更多其他事情而设计的,我们可以在那里做很多事情(我们可以使用异步操作),然后通过调度突变来改变状态。

问题1:Vuejs的开发者为什么决定这样做?

答:

当您的应用程序变得很大,并且有多个开发人员在这个项目上工作时,您会发现“状态管理”(特别是“全局状态”)变得越来越复杂。 Vuex方式(就像react.js中的Redux一样)提供了一种新的机制来管理状态、保持状态和“保存和跟踪”(这意味着每个修改状态的操作都可以被调试工具vue-devtools跟踪)

问题2:“action”和“mutation”有什么区别?

让我们先看看官方的解释:

Mutations: Vuex mutations are essentially events: each mutation has a name and a handler. import Vuex from 'vuex' const store = new Vuex.Store({ state: { count: 1 }, mutations: { INCREMENT (state) { // mutate state state.count++ } } }) Actions: Actions are just functions that dispatch mutations. // the simplest action function increment ({commit}) { commit('INCREMENT') } // a action with additional arguments // with ES2015 argument destructuring function incrementBy ({ dispatch }, amount) { dispatch('INCREMENT', amount) }

以下是我对上述问题的解释:

突变是改变状态的唯一方法 突变并不关心业务逻辑,它只关心“状态” 操作是业务逻辑 动作一次可以提交多个突变,它只实现业务逻辑,不关心数据更改(由突变管理)

根据文件

动作类似于突变,区别在于:

操作不会导致状态突变,而是导致突变。 动作可以包含任意异步操作。

考虑下面的代码片段。

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.
    }
  }
})

动作处理程序(增量)接收一个上下文对象,该对象公开相同的 方法/属性,以便您可以调用 commit来提交一个突变,或者访问状态和getter 通过上下文。State和context.getter

我相信,理解了突变和动作背后的动机,可以让人更好地判断什么时候使用什么以及如何使用。它还将程序员从“规则”变得模糊的情况下的不确定性负担中解放出来。在对它们各自的目的进行了一番推理之后,我得出的结论是,尽管使用动作和突变的方式肯定是错误的,但我不认为存在一个规范的方法。

让我们首先尝试理解为什么我们要经历突变或动作。

为什么一开始要看样板文件?为什么不直接改变组件的状态?

严格地说,您可以直接从组件更改状态。状态只是一个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)
}
...

我认为如果你用样板代替一些短的东西,你会希望样板也小。因此,我假定突变是对状态的本地操作的非常薄的包装,几乎没有业务逻辑。换句话说,突变应该主要像setter一样使用。

现在您已经集中了您的突变,您可以更好地概述您的状态更改,并且由于您的工具(vue-devtools)也知道该位置,因此调试变得更容易。同样值得记住的是,许多Vuex的插件并不直接监视状态来跟踪变化,而是依赖于突变来跟踪变化。因此,对状态的“越界”更改对他们来说是不可见的。

那么突变,行为到底有什么不同呢?

Actions, like mutations, also reside in the store's module and can receive the state object. Which implies that they could also mutate it directly. So what's the point of having both? If we reason that mutations have to be kept small and simple, it implies that we need an alternative means to house more elaborate business logic. Actions are the means to do this. And since as we have established earlier, vue-devtools and plugins are aware of changes through Mutations, to stay consistent we should keep using Mutations from our actions. Furthermore, since actions are meant to be all encompassing and that the logic they encapsulate may be asynchronous, it makes sense that Actions would also simply made asynchronous from the start.

人们经常强调动作可以是异步的,而突变通常不是。您可能决定将这种区别视为一种指示,即突变应该用于任何同步的事情(而操作用于任何异步的事情);然而,如果你需要提交多个突变(同步),或者你需要从突变中获取一个Getter,你就会遇到一些困难,因为突变函数既不接收Getter参数,也不接收突变参数。

...这就引出了一个有趣的问题。

为什么突变不接收getter ?

对于这个问题,我还没有找到一个满意的答案。我看过核心团队的一些解释,但我认为这些解释充其量是毫无意义的。如果我总结它们的用法,getter是对状态的计算(通常是缓存)扩展。换句话说,它们基本上仍然是状态,尽管这需要一些前期计算,而且它们通常是只读的。至少这是他们被鼓励使用的方式。

Thus, preventing Mutations from directly accessing Getters means that one of three things is now necessary, if we need to access from the former some functionality offered by the latter: (1) either the state computations provided by the Getter is duplicated somewhere that is accessible to the Mutation (bad smell), or (2) the computed value (or the relevant Getter itself) is passed down as an explicit argument to the Mutation (funky), or (3) the Getter's logic itself is duplicated directly within the Mutation, without the added benefit of caching as provided by the 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中的一些代码显然是从突变的内部逻辑中渗出来的。

在我看来,这是一种妥协的迹象。我相信允许突变自动接收getter会带来一些挑战。它可以是Vuex本身的设计,也可以是工具(vue-devtools等),或者是维护一些向后兼容性,或者是所有所述可能性的某种组合。

我不相信的是,将getter传递给您自己的突变一定是您正在做错误的事情的标志。我认为这只是“修补”框架的一个缺点。