Reaction

用法: reaction(() => data, (data, reaction) => { sideEffect }, options?)

autorun 的变种,对于如何追踪 observable 赋予了更细粒度的控制。
它接收两个函数参数,第一个(数据 函数)是用来追踪并返回数据作为第二个函数(效果 函数)的输入。
不同于 autorun 的是当创建时效果 函数不会直接运行,只有在数据表达式首次返回一个新值后才会运行。
在执行 效果 函数时访问的任何 observable 都不会被追踪。

效果 函数是可以去抖的,就像 autorunAsync
reaction 返回一个清理函数。
传入 reaction 的函数当调用时会接收两个参数,即当前的 reaction,可以用来在执行期间进行清理。

值得注意的是 效果 函数对数据函数中访问的数据作出反应,这可能会比实际在效果函数使用的数据要少。
此外,效果 函数只会在表达式返回的数据发生更改时触发。
换句话说: reaction需要你生产 效果 函数中所需要的东西。

选项

Reaction 接收第三个参数,它是一个参数对象,有如下可选的参数:

  • context: 传给 reaction 的函数所使用的 this。默认是 undefined(使用箭头函数代替!)。
  • fireImmediately: 布尔值,用来标识效果函数是否在数据函数第一次运行后立即触发。默认值是 false,如果一个布尔值作为传给 reaction 的第三个参数,那么它会被解释为 fireImmediately 选项。
  • delay: 可用于对效果函数进行去抖动的数字(以毫秒为单位)。如果是 0(默认值) 的话,那么不会进行去抖。
  • compareStructural: 默认值是 false。如果是 true 的话,数据 函数的返回值会在结构上与前一个返回值进行比较,并且 效果 函数只有在输出结构改变时才会被调用。也可以通过将 equals 选项设置为 comparer.structural 来指定同样的行为。
  • equals: 默认值是 comparer.default 。如果指定的话,这个比较器函数被用来比较由 数据 函数产生的前一个值和后一个值。只有比较器函数返回 true 效果 函数才会被调用。此选项如果指定的话,会覆盖 compareStructural 选项。
  • name: 字符串,用于在例如像 spy 这样事件中用作此 reaction 的名称。

示例

在下面的示例中,reaction1reaction2autorun1 都会对 todos 数组中的 todo 的添加、删除或替换作出反应。
但只有 reaction2autorun 会对某个 todo 的 title 变化作出反应,因为在 reaction2 的数据表达式中使用了 title,而 reaction1 的数据表达式没有使用。
autorun 追踪完整的副作用,因此它将始终正确触发,但也更容易意外地访问相关数据。
还可参见 MobX 会对什么作出反应?.

  1. const todos = observable([
  2. {
  3. title: "Make coffee",
  4. done: true,
  5. },
  6. {
  7. title: "Find biscuit",
  8. done: false
  9. }
  10. ]);
  11. // reaction 的错误用法: 对 length 的变化作出反应, 而不是 title 的变化!
  12. const reaction1 = reaction(
  13. () => todos.length,
  14. length => console.log("reaction 1:", todos.map(todo => todo.title).join(", "))
  15. );
  16. // reaction 的正确用法: 对 length 和 title 的变化作出反应
  17. const reaction2 = reaction(
  18. () => todos.map(todo => todo.title),
  19. titles => console.log("reaction 2:", titles.join(", "))
  20. );
  21. // autorun 对它函数中使用的任何东西作出反应
  22. const autorun1 = autorun(
  23. () => console.log("autorun 1:", todos.map(todo => todo.title).join(", "))
  24. );
  25. todos.push({ title: "explain reactions", done: false });
  26. // 输出:
  27. // reaction 1: Make coffee, find biscuit, explain reactions
  28. // reaction 2: Make coffee, find biscuit, explain reactions
  29. // autorun 1: Make coffee, find biscuit, explain reactions
  30. todos[0].title = "Make tea"
  31. // 输出:
  32. // reaction 2: Make tea, find biscuit, explain reactions
  33. // autorun 1: Make tea, find biscuit, explain reactions

在下面的示例中,reaction3 会对 counter 中的 count 作出反应。
当调用 reaction 时,第二个参数会作为清理函数使用。
下面的示例展示了 reaction 只会调用一次。

  1. const counter = observable({ count: 0 });
  2. // 只调用一次并清理掉 reaction : 对 observable 值作出反应。
  3. const reaction3 = reaction(
  4. () => counter.count,
  5. (count, reaction) => {
  6. console.log("reaction 3: invoked. counter.count = " + count);
  7. reaction.dispose();
  8. }
  9. );
  10. counter.count = 1;
  11. // 输出:
  12. // reaction 3: invoked. counter.count = 1
  13. counter.count = 2;
  14. // 输出:
  15. // (There are no logging, because of reaction disposed. But, counter continue reaction)
  16. console.log(counter.count);
  17. // 输出:
  18. // 2

粗略地讲,reaction 是 computed(expression).observe(action(sideEffect))autorun(() => action(sideEffect)(expression) 的语法糖。