投身烈火
Keep Walking

MobX入坑指南(2) -- action

上一篇简单介绍了下mobx常用的几个api(observable、computed、autorun),以及mobx-react的api(observer)。这次说说action。

action

用法:

  • action(fn)
  • action(name, fn)
  • @action classMethod
  • @action(name) classMethod
  • @action boundClassMethod = (args) => { body }
  • @action(name) boundClassMethod = (args) => { body }

之前的例子里使用了回调的方式来触发响应,mobx其实也支持使用flux的方式来出发响应,并且在2.2版本提供了action的功能。

action是一个工厂函数,可以接受name和fn两个参数,name是String,主要描述action的作用,fn是Function,是这个action的具体逻辑。action执行后返回一个函数,调用这个函数就会执行action,其实就是调用fn参数。

个人感觉在mobx中,action的作用更多的是用来注释当前的操作……使用的时候在name参数上写操作是干啥的,能够快速的了解action的意图。当然如果安装了devtools,action还能输出调试信息。

需要注意的是,如果使用useStrictapi开启了严格模式,就必须通过action才能修改状态(state),直接修改值会报错。

举个🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {observable, useStrict, action, computed, autorun} from 'mobx';
useStrict(true);
let numbers = observable([1,2,3]);
let sum = computed(() => numbers.reduce((a, b) => a + b, 0));
let disposer1 = autorun(() => console.log(`sum:${sum.get()}`));
// sum:6
let disposer2 = autorun(() => console.log(`length:${numbers.length}`));
// length:3
var pushNumber = action('push number',()=>{
numbers.push(4)
})
pushNumber(4)
// sum:10
// length:4
numbers.push(5)
// Uncaught Error: [mobx] Invariant failed: It is not allowed to create
// or change state outside an `action` when MobX is in strict mode. Wrap
// the current method in `action` if this state change is intended.

另外,action在执行时是可以接受参数的,而且action在执行后还会返回fn参数的返回值,这一点文档里没有明显的说明,害的我翻源码才翻出来的……┑( ̄Д  ̄)┍……话说mobx是用typescript写的呢。虽然ts最近很火,而且功能也确实挺实用的,但是我对java一样死板的语法却怎么都都爱不起来……所以更看好WebAssembly一些……

扯远了扯远了……那么我们再把上面那个例子修改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {observable, useStrict, action, computed, autorun} from 'mobx';
useStrict(true);
let numbers = observable([1,2,3]);
let sum = computed(() => numbers.reduce((a, b) => a + b, 0));
// sum:6
let disposer = autorun(() => console.log(`sum:${sum.get()}`));
var pushNumber = action('push number',(number)=>numbers.push(number))
console.log(`length:${pushNumber(6)}`)
// sum:12
// length:4
console.log(`length:${pushNumber(7)}`)
// sum:19
// length:5

从上面的例子可以看到,传入pushNumber的参数最后被传入了action的fn中,然后pushNumber返回了push的返回值。

官方文档中的一个例子也能看到调用action返回函数时传递参数的应用场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@action createRandomContact() {
this.pendingRequestCount++;
superagent
.get('https://randomuser.me/api/')
.set('Accept', 'application/json')
.end(action("createRandomContact-callback", (error, results) => {
if (error)
console.error(error);
else {
const data = JSON.parse(results.text).results[0];
const contact = new Contact(this, data.dob, data.name, data.login.username, data.picture)
contact.addTag('random-user');
this.contacts.push(contact);
this.pendingRequestCount--;
}
}));
}

在这个🌰中可以看到,action的返回值作为superagent.end的回调,接收error和results两个参数,最后传入fn进行处理。这个例子也引出了另一个问题,action中如何处理一步操作。

async action 和 runInAction

action只能影响正在运行的函数,而无法影响当前函数调用的异步操作。也就是说如果fn中有setTimeout,promise.then,async函数,并且这些函数的回调里对state进行了修改,那么这些回调也应该用action包装一下(在非严格模式下,action和直接修改state值都能生效,所以并不会出现异常,如果是严格模式下就会报错)。

还有,如果要使用async函数作为action,不能直接用action包装async函数,这里需要使用一个tricky,将一个async匿名函数赋值给一个变量或者属性再做包装。

1
2
3
4
5
6
7
8
@action updateDocument = async () => {
const data = await fetchDataFromUrl();
/* required in strict mode to be allowed to update state: */
runInAction("update state after fetching data", () => {
this.data.replace(data);
this.isSaving = true;
})
}

上面的例子还用到了runInAction,它其实就是action(name,fn)()的语法糖,调用后action会立即执行,它的用法是:runInAction(name?, fn, scope?),scope是fn调用时的this指向。注:2.3.0版本以后才能使用。

相关文章