Vuex动作与突变

Kob*_*obi 125 vue.js vuex

在Vuex中,同时拥有"行动"和"突变"的逻辑是什么?

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

"行动"和"突变"之间有什么区别,它们如何协同工作,而且我很好奇为什么Vuex开发人员决定这样做?

Kai*_*cui 173

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

回答:

  1. 当您的应用程序变得庞大,并且当有多个开发人员在这个项目上工作时,您会发现"状态管理"(尤其是"全局状态")将变得越来越复杂.
  2. vuex方式(就像react.js中的Redux一样)提供了一种新的机制来管理状态,保持状态,以及"保存和跟踪"(这意味着每个修改状态的动作都可以通过调试工具跟踪:vue-devtools)

问题2:"行动"和"变异"之间有什么区别?

我们先来看官方解释:

突变:

Vuex突变本质上是事件:每个突变都有一个名称和一个处理程序.

import Vuex from 'vuex'

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    INCREMENT (state) {
      // mutate state
      state.count++
    }
  }
})
Run Code Online (Sandbox Code Playgroud)

操作:操作只是调度突变的函数.

// 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)
}
Run Code Online (Sandbox Code Playgroud)

以下是我对上述内容的解释:

  • 变异是修改状态的唯一方法
  • 变异不关心业务逻辑,它只关心"状态"
  • 行动是商业逻辑
  • 动作一次可以发送多个突变,它只是实现业务逻辑,它不关心数据变化(通过变异管理)

  • 事件"是业务逻辑"并且可以一次发送多个突变这一事​​实是有帮助的.这就是我一直在寻找的答案.谢谢. (59认同)
  • @Kaicui这个答案缺少一个关于突变总是同步的说明,以及可能异步的操作.除此之外,一个很好的答案! (9认同)
  • 你猜的是说你"派遣一个突变".你提交变异的措辞不正确吗? (6认同)
  • 您派遣行动并提交突变. (2认同)
  • 在vue 2.0中,dispatch不再适用于变异,你需要在动作中提交变异. (2认同)

小智 52

变异是同步的,而动作可以是异步的.

换句话说:如果您的操作是同步的,则不需要操作,否则执行它们.

  • _'如果您的操作是同步的,则不需要操作'_:事实并非如此:如果您想从同一个模块中编写多个变异,则**需要**操作,因为您无法从中调用其他操作一种行为。 (4认同)
  • 这实际上回答了我要提出的问题,关于todomvc示例如何不使用动作. (2认同)

Mic*_*oka 16

我相信,了解“突变和动作”背后的动机可以使人们更好地判断何时使用哪种方式。在“规则”变得模糊的情况下,这也使程序员摆脱了不确定的负担。在对它们各自的用途进行了一些推理之后,我得出的结论是,尽管使用操作和变异的方法肯定是错误的,但我认为并没有一种规范的方法。

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

为什么首先要通过样板?为什么不直接在组件中更改状态?

严格来说,您可以state直接从组件中更改。该state仅仅是一个JavaScript对象并没有什么神奇的,将还原更改,你做它。

// Yes, you can!
this.$store.state['products'].push(product)
Run Code Online (Sandbox Code Playgroud)

但是,通过这样做,您将在各处散布状态突变。您将失去仅打开包含状态的单个模块的能力,一目了然地看到可以对其执行何种操作。具有集中化的突变可以解决此问题,尽管要付出一些样板的代价。

// 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)
}
...
Run Code Online (Sandbox Code Playgroud)

我认为,如果您用样板替换一些短的东西,您会希望样板也要小。因此,我认为突变是针对状态本机操作的非常薄的包装,几乎没有业务逻辑。换句话说,突变意味着像塞特犬一样经常使用。

既然您已经集中了突变,就可以更好地了解状态变化,并且由于您的工具(vue-devtools)也知道该位置,因此使调试更加容易。还需要记住的是,许多Vuex的插件并不直接监视状态来跟踪更改,而是依赖于突变。因此,他们看不到状态的“越界”更改。

那么mutationsactions到底有什么区别呢?

动作(如突变)也驻留在商店的模块中,并且可以接收state对象。这意味着,他们可能还直接发生变异它。那么,两者都具有什么意义呢?如果我们认为必须使变异保持小而简单,则意味着我们需要一种替代方法来容纳更详尽的业务逻辑。行动是做到这一点的手段。而且由于我们早先已经建立了,vue-devtools和插件知道通过Mutations进行的更改,为了保持一致,我们应该继续从操作中使用Mutations。此外,由于动作应包含所有内容,并且它们封装的逻辑可以是异步的,因此从一开始就将动作也简单地设为异步是有意义的。

人们经常强调动作可以是异步的,而变异通常不是异步的。您可能会决定将这种区别看作是对任何同步对象(对于任何异步对象,都应使用动作)的一种指示。但是,例如,如果您需要(同步)提交多个突变,或者需要使用突变中的Getter,则您会遇到一些困难,因为突变函数既不接受Getter也不接受Mutations作为参数。

...导致一个有趣的问题。

为什么突变不接受吸气剂?

对于这个问题,我还没有找到满意的答案。我已经看到核心团队的一些解释,认为我充其量是最好的。如果我总结一下它们的用法,则应该将Getter扩展为该状态的扩展(通常是缓存的)。换句话说,它们基本上仍然是状态,尽管需要进行一些前期计算,并且它们通常是只读的。至少这是鼓励它们被使用的方式。

因此,阻止Mutations直接访问Getters意味着,如果我们需要从前者访问后者提供的某些功能,那么现在有三件事之一是必要的:(1)Getter提供的状态计算要么在某个可以访问的地方重复进行,到Mutation(难闻的气味),或(2)将计算值(或相关的Getter本身)作为显式变元传递给Mutation(笨拙),或(3)Getter的逻辑本身直接在Mutation中重复,而没有Getter(恶臭)提供的缓存优势。

以下是(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 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我看来,以上内容不仅令人费解,而且有些“漏水”,因为“动作”中存在的某些代码显然是从“变异”的内部逻辑中渗出的。

我认为,这表明存在妥协。我认为,允许Mutations自动接收Getters会带来一些挑战。它可以是Vuex本身的设计,也可以是工具(vue-devtools等)的设计,或者是保持一定的向后兼容性,或者是所有陈述的可能性的某种组合。

我不相信自己将Getters传递给您的Mutations必然表明您做错了事。我认为这只是“修补”框架的缺点之一。

  • 对我来说,这是最好的答案。只有在读完之后,我才有了当你觉得你理解了某件事时的那种“咔哒”感觉。 (2认同)

小智 13

我认为TLDR的答案是Mutations意味着同步/事务性.因此,如果您需要运行Ajax调用或执行任何其他异步代码,则需要在Action中执行此操作,然后在设置新状态之后提交变异.

  • 这看起来像是对文档的总结;这没有什么问题。然而,这个答案的问题在于它所断言的不一定是真的。您可以在调用异步函数/AJAX 时修改突变内的状态,然后可以在完整回调中更改状态。我认为这就是为什么在使用 Vuex 时*应该*将动作用于最佳开发实践的原因造成了如此多的困惑。我知道当我开始使用 Vuex 时,这对我来说肯定是一个困惑的根源。 (3认同)

Ale*_*lex 9

我已经专业地使用 Vuex 大约 3 年了,这就是我认为我已经弄清楚了 action 和 mutations 之间的本质区别,你如何从一起使用它们中受益,以及如果你可以让你的生活更艰难不好好用。

Vuex 的主要目标是提供一种新模式来控制应用程序的行为:反应性。这个想法是将应用程序状态的编排卸载到一个专门的对象:商店。它方便地提供了将您的组件直接连接到您的商店数据以方便使用的方法。这允许您的组件专注于它们的工作:定义模板、样式和基本组件行为以呈现给您的用户。同时,存储处理繁重的数据负载。

但这不仅仅是这种模式的唯一优势。存储是整个应用程序的单一数据源这一事实提供了跨多个组件重用这些数据的巨大潜力。这不是第一个尝试解决跨组件通信问题的模式,但它的亮点在于它通过基本上禁止组件修改此共享数据的状态来强制您对应用程序实施非常安全的行为,并强制它改为使用“公共端点”来请求更改。

基本思想是这样的:

  • store 有一个内部状态,永远不应该被组件直接访问(mapState 被有效禁止)
  • store 有突变,它是对内部状态的同步修改。突变的唯一工作是修改状态。它们只能从一个动作中被调用。它们应该被命名来描述发生在状态中的事情(ORDER_CANCELED、ORDER_CREATED)。保持它们简短而甜蜜。您可以使用 Vue Devtools 浏览器扩展来逐步完成它们(它也非常适合调试!)
  • 商店也有动作,应该是异步的或返回一个承诺。它们是您的组件在想要修改应用程序状态时将调用的操作。它们应该以面向业务的动作(动词,即cancelOrder、createOrder)命名。这是您验证和发送请求的地方。如果需要更改状态,每个操作可能会在不同的步骤调用不同的提交。
  • 最后,商店有 getter,您可以使用它们将状态暴露给组件。随着应用程序的扩展,它们会在许多组件中大量使用。Vuex 大量缓存 getter 以避免无用的计算周期(只要你不向 getter 添加参数 - 尽量不要使用参数)所以不要犹豫,广泛使用它们。只要确保您提供的名称尽可能接近应用程序当前所处的状态。

话虽如此,当我们开始以这种方式设计我们的应用程序时,魔法就开始了。例如:

  • 我们有一个组件可以向用户提供订单列表,并可以删除这些订单
  • 组件映射了一个 store getter (deletableOrders),它是一个带有 id 的对象数组
  • 该组件在每一行订单上都有一个按钮,它的点击被映射到一个商店动作(deleteOrder),该动作将订单对象传递给它(我们会记住,它来自商店的列表本身)
  • store deleteOrder 操作执行以下操作:
    • 它验证删除
    • 它存储临时删除的订单
    • 它用订单提交 ORDER_DELETED 突变
    • 它发送 API 调用以实际删除订单(是的,在修改状态之后!)
    • 它等待调用结束(状态已经更新)并且在失败时,我们使用我们之前保留的顺序调用 ORDER_DELETE_FAILED 突变。
  • ORDER_DELETED 突变将简单地从可删除订单列表中删除给定订单(这将更新 getter)
  • ORDER_DELETE_FAILED 突变只是将其放回原处,并修改状态以通知错误(另一个组件错误通知将跟踪该状态以了解何时显示自身)

最后,我们有一种被视为“反应式”的用户体验。从我们用户的角度来看,该项目已被立即删除。大多数时候,我们希望我们的端点能够正常工作,所以这是完美的。当它失败时,我们仍然可以控制我们的应用程序将如何反应,因为我们已经成功地将前端应用程序的状态与实际数据的关注点分开了。

请注意,您并不总是需要商店。如果您发现您正在编写如下所示的商店:

export default {
  state: {
    orders: []
  },
  mutations: {
    ADD_ORDER (state, order) {
       state.orders.push(order)
    },
    DELETE_ORDER (state, orderToDelete) {
       state.orders = state.orders.filter(order => order.id !== orderToDelete.id)
    }
  },
  actions: {
    addOrder ({commit}, order) {
      commit('ADD_ORDER', order)
    },
    deleteOrder ({commit}, order) {
      commit('DELETE_ORDER', order)
    }
  },
  getters: {
    orders: state => state.orders
  }
}
Run Code Online (Sandbox Code Playgroud)

在我看来,您似乎只是将存储用作数据存储,并且可能错过了它的反应性方面,因为它不让它也控制您的应用程序响应的变量。基本上,您可以而且应该将在您的组件中编写的一些代码行卸载到您的商店。


Gop*_*pan 6

突变:

Can update the state. (Having the Authorization to change the state).
Run Code Online (Sandbox Code Playgroud)

行动:

Actions are used to tell "which mutation should be triggered"
Run Code Online (Sandbox Code Playgroud)

以 Redux 方式

Mutations are Reducers
Actions are Actions
Run Code Online (Sandbox Code Playgroud)

为什么都是??

当应用程序增长时,编码和行数会增加,那个时候你必须处理 Actions 中的逻辑而不是突变,因为突变是改变状态的唯一权限,它应该尽可能干净。


ube*_*kel 5

免责声明 - 我刚开始使用vuejs所以这只是我推断设计意图.

时间机器调试使用状态的快照,并显示动作和突变的时间线.从理论上讲,我们可能只是actions记录状态设定者和吸气剂同步描述突变.但是之后:

  • 我们会得到不纯的输入(异步结果),这会导致设置者和吸气剂.这在逻辑上很难遵循,不同的异步设置器和getter可能会令人惊讶地相互影响.这仍然可以发生在mutations交易中,但我们可以说交易需要改进,而不是作为行动中的竞争条件.动作中的匿名突变可以更容易地重现这些类型的错误,因为异步编程是脆弱和困难的.
  • 事务日志很难阅读,因为状态更改没有名称.它将更像代码,更少英语,缺少突变的逻辑分组.
  • 仪器记录数据对象上的任何突变可能比较棘手且性能较差,而现在与突变函数调用之前和之后存在同步定义的差异点相反.我不确定这个问题有多大.

将以下事务日志与命名突变进行比较.

Action: FetchNewsStories
Mutation: SetFetchingNewsStories
Action: FetchNewsStories [continuation]
Mutation: DoneFetchingNewsStories([...])
Run Code Online (Sandbox Code Playgroud)

使用没有命名突变的事务日志:

Action: FetchNewsStories
Mutation: state.isFetching = true;
Action: FetchNewsStories [continuation]
Mutation: state.isFetching = false;
Mutation: state.listOfStories = [...]
Run Code Online (Sandbox Code Playgroud)

我希望你能从这个例子中推断出行为中异步和匿名变异的潜在增加的复杂性.

https://vuex.vuejs.org/en/mutations.html

现在假设我们正在调试应用程序并查看devtool的变异日志.对于记录的每个突变,devtool将需要捕获状态的"之前"和"之后"快照.但是,上面示例变异中的异步回调使得这是不可能的:当提交变异时,回调函数尚未被调用,并且devtool无法知道何时实际调用回调 - 在回调中执行任何状态变异基本上是不可跟踪的!


Abd*_*han 5

根据 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.
    }
  }
})
Run Code Online (Sandbox Code Playgroud)

操作处理程序(增量)接收一个上下文对象,该对象在商店实例上公开同一组方法/属性,因此您可以调用context.commit来提交变异,或者通过context.state和context.getters访问状态和getter.


rol*_*oli 5

动作和突变之间的主要区别是:

  1. 在动作内部,您可以运行异步代码,但不能运行突变。因此,请对异步代码使用动作,否则请使用变异。
  2. 在动作内部,您可以访问获取器,状态,突变(提交它们),动作(调度它们)以访问状态。因此,如果您只想访问状态,请使用突变,否则请使用操作。