Actions

在 MobX 中,Action 就是任意一段可以修改 State 的代码。Action 所作的事情,一般就是对一个事件进行响应,然后再改变 State 的内容。

一个函数被标记为 Action 的原则是这个函数仅会修改 State,但是并不会派生出其他任何信息。使用 makeAutoObservable() 会自动检查并标记一部分 Action,但是比较复杂的 Action 还是需要手工标记的。Action 都是在 MobX 的内部的事务中运行,这就保证任何可悲观察的对象在最外层的 Action 完成之前都不会被更新,而且任何 Action 执行期间产生的中间值也都不会被泄露给应用。

将一个类函数标记为 Action 的示例可以参考上一节的示例。为了尽可能利用 MobX 的事务,Action 应该被尽可能的传递到外层。一个没有被标记的、会连续调用两个 Action 的事件处理函数仍然会生成两个事务,所以将事件处理函数也标记为 Action 就成了一个不错的选择,因为事件处理函数是处于最外层的。要标记事件处理函数,就需要借助 action 函数。

Tip

没有错,action 函数就是用来标记 Action 的那个注解标记。

action 作为函数来使用的时候,可以返回一个与原函数拥有相同签名的被包装过的新函数。例如可以这样包装一个事件处理函数。

function ResetButton({ formState }) {
  return (
    <button
      onClick={action(e => {
        formState.resetValues();
        e.stopPropagation();
      })}>
      Reset Form
    </button>
  );
}

在使用 makeAutoObservable() 的时候,使用配置 { autoBind: true } 可以使方法被绑定到正确的实例,确保 this 的正确。

action 功能类似的包装函数是 runInAction() 这个包装函数可以创建一个会被立刻调用的临时 Action,经常会被用在异步进程中。

对于异步 Action 来说,MobX 通常不需要做任何特殊处理,因为可观察对象是可变的,在 Action 中保持对可观察对象的引用一般都是安全的。但是依旧有一点需要注意,异步 Action 中更新可观察对象的每一个步骤都应该被标记为 Action。对于这个概念,最典型的应用案例就是经常要使用到的 async/await 函数。

import { runInAction, makeAutoObservable } from 'mobx';

class Store {
  data = [];
  state = 'pending';

  constructor() {
    makeAutoObservable(this);
  }

  async fetchData() {
    this.data = [];
    this.state = 'pending';
    try {
      const data = await fetchFromServer();
      const filteredData = filterRecently(data);
      runInAction(() => {
        this.data = filteredData;
        this.state = 'done';
      });
    } catch (e) {
      runInAction(() => {
        this.state = 'error';
      });
    }
  }
}

在上面这个示例中,await 之后的任何操作都不跟它在一个步骤中,所以对可观察对象做出的任何变更都需要包裹在 Action 中。如果使用的不是 async/await 语法,而是使用的生成器语法,即使用 yield 代替 await ,则需要在 makeAutoObservable 中将方法标记为 flow