前言
mobx的action中调用异步函数的时候,会有一个“陷阱”。下面举例说明
假设有这样一个类
class Store {
@observable test1 = 0
@observable test2 = 0
@computed get testCom() {
return this.test1 + this.test2
}
constructor() {
autorun(() => {
console.log("autorun: ", this.testCom)
})
}
以及一个异步操作。
function getDataFromServer() {
return new Promise(reslove => {
setTimeout(() => {
console.log("请求完毕返回yangguang");
reslove("yangguang")
}, 1000);
})
}
如果像下面这样去编写action并调用的话,autorun会执行几次呢?
class Store {
@observable test1 = 0
@observable test2 = 0
@computed get testCom() {
return this.test1 + this.test2
}
constructor() {
autorun(() => {
console.log("autorun: ", this.testCom)
})
@action.bound getDatas() {
console.log("开始请求数据")
this.test1 = 0
const data = getDataFromServer().then((data) => {
console.log(`拿到数据了:${data}`)
this.test1 = 55
this.test2 = 155
})
console.log("请求结束")
this.test1 = 666
}
}
答案是3次,结果如下图
原因在于,then()
所包裹的回调函数并不属于getDatas
方法的一部分,这个回调是由promise去调用的,而不是getDatas
方法。换句话说,下图中红框圈出的部分是同步代码,而绿框圈出的部分是异步代码,两者属于不同的栈,@action
只能作用于同步代码这部分,所以this.test1 = 0
与this.test1 = 666
两个对test1的操作被合并了,而回调函数中对数据操作的语句this.test1 = 55;this.test2 = 155
,并没有被@action
覆盖到,于是就理所当然的触发了两次autorun(就像不使用action那样)
原因既然找到了,那解决起来就简单了。下面给出三种解决办法(写法),其实这三种也是大家一下子就能想到的。
action/runInAction
第一种:将设置state的语句抽离成单独的action。如下图
第二种:使用action方法
唯一的缺点是回调函数所有代码都放在action中了,如果仍然希望只将修改数据的部分放在action中,可以使用runInAction。
第三种:使用action的语法糖runInAction
这三种的打印结果完全相同,如下图:
打印结果如下:
async/await
但其实上面那三种方法都很啰嗦,使用es6的async/await会让代码清爽一些
大家觉得这样调用会输出什么呢?还和前三种方法一样吗?当然不一样,甚至会让人有些费解。请看下图
先说明一件事:await/async只是promise的语法糖,因此@action
同样只能负责到await之前的代码,await之后的代码就不归@action
管了。所以this.test1 = 100;this.test1 = 123
被合并了,而后面的四次修改没有被合并,触发了四次autorun。知道了原因修改起来就简单了,修改后的代码如下:
控制台打印结果如下:
flow
当当当当~ 主角登场,前面说了那么多其实只是想让大家知道其他方法有多么的麻烦。来看看使用mobx提供的flow方法会让代码有多么简单和清爽,使用flow之后代码如下(别忘了在文件头引入flow方法):
输入如下:
看看,整个getDatasFlows方法没有使用任何action,无比的清爽干净!请看下图,相同颜色框框内的数据操作被合并了,至于原因刚刚也说过了。可以理解为yield之后的每一个框框都被一个runInAction方法包裹住了。
以上,谢谢阅读!