高性能 MobX 模式(part 2)- 响应变化

发表于:July 24, 2017 at 08:18 PM

在 Part 1 我们看到如何去建立一个 MobX 状态树并且让它可观察。在这个基础上,我们下一步要做的是开始响应数据的变化。坦白说,有趣的部分是从这里开始的。

译者:阿里云前端-也树

原文链接:

MobX 可以保证,无论何时你的响应式数据发生了变化,相应的依赖于 observable 的属性会自动同步更新。这意味着你现在可以专注于响应变化和处理变化带来的副作用,而不需要为数据的同步操心。

让我们深入一下,看看有哪些方式可以让你处理副作用。

使用 @action 作为入口

默认情况下,当你修改被监听的变量值,MobX 能够发觉并且保持其它依赖这个变量的值同步更新。这个过程是同步发生的。然而在很多情况下,你会想要在一个方法里修改多个被监听的变量值。这会导致许多通知被触发并且有可能会使你的应用运行变慢。

将你要触发的方法放在一个 action() 中是一个更好的做法。这会在你的方法上创建一个数据处理的边界,所有受影响被观察的变量会在你的 action 执行后保持同步更新。注意这个延迟后的通知只会对当前函数作用域下被观察的变量生效。如果你有许多会更改被观察变量的异步 action,就需要把他们放在 runInAction() 方法里了。

class Person {
  @observable firstName;
  @observable lastName;

  // 因为我们用 @action 装饰器来装饰这个方法,fullName 只会在 changeName() 执行完成后改变。
  @action
  changeName(first, last) {
    this.firstName = first;
    this.lastName = last;
  }

  @computed get fullName() {
    return `${this.firstName}, ${this.lastName}`;
  }
}

const p = new Person();
p.changeName("Pavan", "Podila");

action 是 store 产生变化的入口。通过使用 action 方法,你可以让更新多个被观察的变量变成原子性的操作。

tip: 尽可能避免直接从外界操作被观察的变量,同时显式的为你做数据变化的方法添加 @action 装饰器。事实上,你可以通过设置 useStrict(true) 来强制禁止在外界操作。

使用 autorun 来触发副作用

MobX 可以保证被观察变量的结构是稳定不变的。但是如果 MobX 的世界里只有被观察到的变量,是不是太无趣了一点。我们需要它们的一个对应产物 observers 来让事情有趣一点。

事实上,UI 就是 MobX store 美化后的一名观察者。通过使用 mobx-react 可以让你的 React 组件观察对应 store 的变动并且无论何时 store 发生了变化,这些组件会自动重新渲染。

但是,UI 不是你系统中唯一的观察者。你可以在你的 store 上添加更多的观察者,让它们去做一些有趣的事情。console-logger 可以作为一名最基础的观察者,它的功能就是在观察的变量有变更时,把变更后的值打印出来。

通过 autorun 方法我们可以非常容易的创建一名观察者。最快的方式就是给 autorun 方法提供一个函数。无论你在这个函数里用到了哪些被观察的变量,它们都会自动地被 MobX 追踪。一旦这些变量发生变化,你提供的函数会再次执行,也就是 autorun 的功能。

class Person {
  @observable firstName = "None";
  @observable lastName = "None";

  constructor() {
    // 一个简单的控制台日志功能
    autorun(() => {
      console.log(`Name changed: ${this.firstName}, ${this.lastName}`);
    });

    // 这里也会触发一次 autorun
    this.firstName = "Mob";

    // 这里又触发了一次 autorun
    this.lastName = "X";
  }
}

// 控制台输出: Name changed: None, None
// 控制台输出: Name changed: Mob, None
// 控制台输出: Name changed: Mob, X

上面的日志你也看到了,autorun方法会立即触发,并且每一次的触发都会有追踪的变量发生变化。但如果你仅仅想要在被观察的变量有变化的时候触发,而不想立即去执行 autorun,这个时候要怎么办呢?继续往下读,我们有 reaction 方法来解决这个问题。

使用 reaction 方法,在初次数据变化后触发副作用

reaction 相较于 autorun 提供了更符合直觉的控制方式。首先,它不会立即执行,而是会等到追踪的被观察变量第一次发生变化后才会执行。它的 API 相较于 autorun 也有微小的区别,最简单的情况你要提供两个参数:

reaction(
  () => data,
  data => {
    /* side effect */
  }
);

第一个函数(追踪函数)需要返回用来追踪的数据 data。data 会被传入第二个函数(效果函数)。效果函数是不会被追踪的,你在这里可以随意的使用其它被追踪的变量。

默认情况下,reaction 第一次是不会执行的,它会等待追踪函数的变化。只有当追踪函数返回的 data 发生变化,副作用才会被产生。通过把之前的 autorun 分割为追踪函数和效果函数,你可以对真正产生副作用的部分有更好的控制。

import { reaction } from "mobx";

class Router {
  @observable page = "main";

  setupNavigation() {
    reaction(
      () => this.page,
      page => {
        switch (page) {
          case "main":
            this.navigateToUrl("/");
            break;

          case "profile":
            this.navigateToUrl("/profile");
            break;

          case "admin":
            this.navigateToUrl("/admin");
            break;
        }
      }
    );
  }

  navigateToUrl(url) {
    /* ... */
  }
}

在上面的示例中,当我加载 main 页面的时候,我不想要触发导航。这是一个使用 reaction 的绝佳示例。只有当 Router 类中的 page 属性发生变化,才会导航到指定的 url。

以上是一个非常简单的路由功能,只包含了几个固定的页面。你可以通过添加一个 pageURL 的 map 结构来增强这个路由的可拓展性。通过这种方式,路由(伴随着 URL 改变)变成了更改你 store 中的某个属性产生的副作用。

使用 when 来触发一次性的副作用

autorunreaction 维护的是长期的副作用。当你初始化应用时你可能会创建一次性的副作用并且会期望它们在应用的生命周期内起作用。

有件事我之前一直没有提到,autorunreaction 都会返回一个 disposer 函数。你可以触发 disposer 并且在任何时候取消那些副作用。

const disposer = autorun(() => {
  /* 这里是副作用,基于追踪的被观察变量 */
});

// .... 之后的某个时间
disposer(); // 取消 autorun

现在我们构建的应用有很多使用场景。你可能会仅仅想要在你的应用达到某个条件的时候去触发特定的副作用。同时你想要这些特定的副作用只出现一次,之后再也不出现。

让我们看一个具体的例子:比如说,你想要在用户达到一个里程碑的时候显示一条信息。这个里程碑对任何用户来说只会出现一次,所以你不想创建一个像 autorunreaction这样的长期维护的副作用。是时候拿出 when 这个 API 来完成这项工作了。

when 需要两个参数,和 reaction 类似。第一个参数(追踪函数)需要返回一个布尔值。当返回值为 true,when 方法的第二个参数(效果函数)会执行。最棒的部分是 when 方法在执行之后会自动 dispose。所以没必要再去定义 disposer 函数同时人工触发它。

when(
  () => this.reachedMilestone,
  () => {
    this.showMessage({ title: "Congratulations", message: "You did it!" });
  }
);

故意忽略的部分

虽然我只提到了 actionautorunreactionwhen 方法,还有一些 MobX 提供给我们的 API 可以用来处理更进阶的场景。因为大部分场景下 MobX 只需要使用上面提及的 API,所以我故意暂时忽略了剩下的那些 API。一旦你可以自如的运用上面这些方法,剩下的是非常容易理解和学习的。这些是根基,在开始建设先进和华丽的上层建筑之前,我们要把这些烂熟于心。

我可能会为高阶 API 单独写一篇文章。也许是 Part-4。:-)

功能强大的开发套件

到目前为止,我们已经看到过许多去追踪对象变化并且响应这些变化的技术。Mobx 实现了更高一层的抽象,让我们可以从更高的层级去思考,而不需要去担心追踪和响应变化的意外复杂性。

我们现在有一个依靠领域模型变化的地基,在这之上,我们可以构建健壮的系统。把所有在领域模型之外的部分都当做副作用,我们可以提供视觉反馈(UI),同时也可以提供像监控、数据分析、日志等等更多的其它功能活动。

未完待续…

在 Part 3,我们会看到很多示例,以便于我们去练习我们目前学到的东西。