Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 37 题:为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作 #65

Open
yygmind opened this issue Mar 27, 2019 · 13 comments
Labels

Comments

@yygmind
Copy link
Contributor

yygmind commented Mar 27, 2019

No description provided.

@yygmind yygmind changed the title 第 37 题 第 37 题 为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作 Mar 27, 2019
@zeroone001
Copy link

Vuex的异步操作可以放在action里面,用Promise或者async,await都是不错的选择

@bohaixv
Copy link

bohaixv commented Mar 28, 2019

应该是各自对于状态管理机制的一种涉及。vue和redux都是一种状态管理机制。 然后他们会有自己的state、和修改state的方法, 修改state的方法涉及到同步和异步,vuex的处理方式是同步在mutation里面,异步在actions里面,然后redux的同步就是reducer ,异步更多的是用户自己去通过中间件的方式去实现的把。 没写过redux 我只能理解到这里了。

@Hiker9527
Copy link

vue用的不是很多,所以不是很清楚mutation里面为什么不能有异步操作,下面解释一下为什么Redux的reducer里不能有异步操作。

  1. 先从Redux的设计层面来解释为什么Reducer必须是纯函数

如果你经常用React+Redux开发,那么就应该了解Redux的设计初衷。Redux的设计参考了Flux的模式,作者希望以此来实现时间旅行,保存应用的历史状态,实现应用状态的可预测。所以整个Redux都是函数式编程的范式,要求reducer是纯函数也是自然而然的事情,使用纯函数才能保证相同的输入得到相同的输入,保证状态的可预测。所以Redux有三大原则:

  • 单一数据源,也就是state
  • state 是只读,Redux并没有暴露出直接修改state的接口,必须通过action来触发修改
  • 使用纯函数来修改state,reducer必须是纯函数
  1. 下面在从代码层面来解释为什么reducer必须是纯函数

那么reducer到底干了件什么事,在Redux的源码中只用了一行来表示:

currentState = currentReducer(currentState, action)

这一行简单粗暴的在代码层面解释了为什么currentReducer必须是纯函数。currentReducer就是我们在createStore中传入的reducer(至于为什么会加个current有兴趣的可以自己去看源码),reducer是用来计算state的,所以它的返回值必须是state,也就是我们整个应用的状态,而不能是promise之类的。

要在reducer中加入异步的操作,如果你只是单纯想执行异步操作,不会等待异步的返回,那么在reducer中执行的意义是什么。如果想把异步操作的结果反应在state中,首先整个应用的状态将变的不可预测,违背Redux的设计原则,其次,此时的currentState将会是promise之类而不是我们想要的应用状态,根本是行不通的。

其实这个问题应该是Redux中为什么不能有副作用的操作更合适。

@Vikingama
Copy link

因为异步操作是成功还是失败不可预测,什么时候进行异步操作也不可预测;当异步操作成功或失败时,如果不 commit(mutation) 或者 dispatch(action),Vuex 和 Redux 就不能捕获到异步的结果从而进行相应的操作

@lvzhiyi
Copy link

lvzhiyi commented Aug 19, 2019

因为更改state的函数必须是纯函数,纯函数既是统一输入就会统一输出,没有任何副作用;如果是异步则会引入额外的副作用,导致更改后的state不可预测;

@fruitful01
Copy link

vuex中为什么把把异步操作封装在action,把同步操作放在mutations? - 尤雨溪的回答 - 知乎

@yygmind yygmind added the Vue label Dec 16, 2019
@yygmind
Copy link
Contributor Author

yygmind commented Dec 19, 2019

Mutation 必须是同步函数
一条重要的原则就是要记住 mutation 必须是同步函数。为什么?请参考下面的例子:

mutations: {
  someMutation (state) {
    api.callAsyncMethod(() => {
      state.count++
    })
  }
}

现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

在组件中提交 Mutation
你可以在组件中使用 this.$store.commit('xxx') 提交 mutation,或者使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用(需要在根节点注入 store)。

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

参考:Mutation 必须是同步函数

@tenadolanter
Copy link

tenadolanter commented Dec 25, 2019

中文翻译可能有些偏差(不是我翻的)。区分 actions 和 mutations 并不是为了解决竞态问题,而是为了能用 devtools 追踪状态变化。

事实上在 vuex 里面 actions 只是一个架构性的概念,并不是必须的,说到底只是一个函数,你在里面想干嘛都可以,只要最后触发 mutation 就行。异步竞态怎么处理那是用户自己的事情。vuex 真正限制你的只有 mutation 必须是同步的这一点(在 redux 里面就好像 reducer 必须同步返回下一个状态一样)。

同步的意义在于这样每一个 mutation 执行完成后都可以对应到一个新的状态(和 reducer 一样),这样 devtools 就可以打个 snapshot 存下来,然后就可以随便 time-travel 了。如果你开着 devtool 调用一个异步的 action,你可以清楚地看到它所调用的 mutation 是何时被记录下来的,并且可以立刻查看它们对应的状态。其实我有个点子一直没时间做,那就是把记录下来的 mutations 做成类似 rx-marble 那样的时间线图,对于理解应用的异步状态变化很有帮助。

#照搬过来,给更多人看到

作者:尤雨溪
链接:https://www.zhihu.com/question/48759748/answer/112823337
来源:知乎

@yygmind yygmind changed the title 第 37 题 为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作 第 37 题:为什么 Vuex 的 mutation 和 Redux 的 reducer 中不能做异步操作 Dec 31, 2019
@ooo1l
Copy link

ooo1l commented May 17, 2020

vuex 的mutaion操作通过commit进行触发,
在commit方法的内部通过_withComit方法对state数据进行修改
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
}),
同时内部维护了一个this._subscribers 的订阅中心,当有mutation被执行的时候,commit会触发this._subscribers 中的函数
if (!options || !options.silent) {
this._subscribers.forEach(sub => sub(mutation, this.state))
}
那么在进行异步操作的时候,就不会准备的捕捉到修改后的state的状态,从而导致依赖发布订阅模式实现的logger 或者 devtools都不能准备的捕捉状态

下面的描述来自于vuex的文档
现在想象,我们正在 debug 一个 app 并且观察 devtool 中的 mutation 日志。每一条 mutation 被记录,devtools 都需要捕捉到前一状态和后一状态的快照。然而,在上面的例子中 mutation 中的异步函数中的回调让这不可能完成:因为当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。

@Murphycx94
Copy link

vuex中在mutation中使用异步,其实对结果是没有影响的
只是人为规定不能在mutation中使用异步

  1. 为了devtool快照可以正确记录值的变化
  2. 设计理念,将有副作用的函数放在action中,同步修改放在mutation中

@m7yue
Copy link

m7yue commented Sep 10, 2020

单一职责原则:mutation 或 reducer 只作为数据的输入输出, 即 state 的变更接口,纯函数。这样做的好处就是可以更好解耦和接口封装。

@Mrfujianfei
Copy link

首先明确一点就是:
mutaition, reducer其实就是个函数,没有明确规定不能做异步。江湖百态,并不全是武当派浩气长存,也有星宿派法力无边。看你怎么用了。
官方只是规定,或者说强行建议你在这里面做同步操作,将它作为一个无副作用的函数,使得你的状态可预测的,方便维护。
总的来说,这是一种规范。但在这里面做异步程序也不会报错。
反正我是武当派。

@MakeBetterMe
Copy link

可以是可以,只是不推荐这么做,否则在vue的dev-tools中,无法方便的追踪一个数据的上下文,因为不知道哪个修改在前,哪个修改在后,时间线会乱掉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests