MobX 原理分析
先来看一个典型的 mobx + react 例子。在 jsfiddle 里打开
import { observable } from 'mobx';
import { observer } from 'react-mobx';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
const appState = observable({
count: 0,
});
appState.increment = function() {
this.count ++;
};
appState.decrement = function() {
this.count --;
};
@observer
class Count extends Component {
render() {
return (<div>
Counter: { appState.count } <br />
<button onClick={this.handleInc}> + </button>
<button onClick={this.handleDec}> - </button>
</div>);
}
handleInc() {
appState.increment();
}
handleDec() {
appState.decrement();
}
}
ReactDOM.render(<Count />, document.getElementById('root'));
这个例子里,先通过 mobx 定义了 appState,Count 的 render 执行时里引用 appState 的数据。然后如果用户点击 + 或 - 按钮,会触发 appState 的修改,appState 的修改会自动触发 Counter 的更新。
基本原理
而要理解 mobx 的原理,我们需要一个更底层的例子。
import { observable, autorun } from 'mobx';
const counter = observable(0);
autorun(() => {
console.log('autorun', counter.get());
});
counter.set(1);
运行结果是:
autorun 0
autorun 1
大家可能会好奇,为什么 counter.set()
之后,autorun
会自动执行? 要达到这个目的,通过 counter
需要知道 autorun
是依赖他的。那么这个依赖关系是在什么时候以及如何生成的呢?
先看代码,这里涉及了 mobx 的 observable 和 autorun 接口。与此相关的有 Observable 和 Derivation 两个类。Observable 是数据源,Derivation 是推导。
类定义如下:
Observable
- observing: [Derivation]
- get()
- set()
Derivation
- observer: [Observable]
然后,autorun
执行的步骤是这样的:
- 生成一个 Derivation
- 执行传入函数,计算出 observing
- 怎么计算? 访问数据时会走到 Observable 的 get() 方法,通过 get() 方法做的记录
- 在 observing 的 Observable 的 observer 里添加这个 Derivation
到这里,Observable 和 Derivation 的依赖关联就建立起来了。
那么 counter.set()
执行之后是如何触发 autorun
自动执行? 在有了上面这一层依赖关系之后,这个就很好理解了。counter.set()
执行时会从自己的 observing 属性里取依赖他的 Derivation,并触发他们的重新执行。
运行时依赖计算
再看一个例子。
import { observable, autorun } from 'mobx';
const counter = observable(0);
const foo = observable(0);
const bar = observable(0);
autorun(() => {
if (counter.get() === 0) {
console.log('foo', foo.get());
} else {
console.log('bar', bar.get());
}
});
bar.set(10); // 不触发 autorun
counter.set(1); // 触发 autorun
foo.set(100); // 不触发 autorun
bar.set(100); // 触发 autorun
执行结果:
foo 0
bar 10
bar 100
autorun 先是依赖 counter 和 foo,然后 counter 设为 1 之后,就不依赖 foo,而是依赖 counter 和 bar 了。所以之后修改 foo 并不会触发 autorun 。
那么 mobx 是如何在运行时计算依赖的呢?
实际上前面的 autorun
的执行步骤是做了简化的,真实的是这样:
- 生成一个 Derivation
- 记录 oldObserving
(+)
- 执行传入函数,计算出 observing
- 怎么计算? 访问数据时会走到 Observable 的 get() 方法,通过 get() 方法做的记录
- 和 oldObserving 做 diff,得到新增和删除列表
(+)
- 通过前面得到的 diff 结果,修改 Observable 的 observing
相比之前的,增加了 diff 的逻辑,以达到每次执行的时候动态更新依赖关系表的目的。
get/set magic
大家在看前面的例子里可能会有个疑问,为啥第一个例子里可以通过 appState.counter
来设置,而后面的例子里需要用 counter.get
和 counter.set
来取值和设值?
这和数据类型有关,mobx 支持的类型有 primitives, arrays, classes 和 objects 。primitives (原始类型) 只能通过 set 和 get 方法取值和设值。而 Object 则可以利用 Object.defineProperty
方法自定义 getter 和 setter 。
Object.defineProperty(adm.target, propName, {
get: function() { return observable.get(); },
set: ...
});
详见源码。
ComputedValue
ComputedValue 同时实现了 Observable 和 Derivation 的接口,即可以监听 Observable,也可以被 Derivation 监听。
Reaction
Reaction 本质上是 Derivation,但他不能再被其他 Derivation 监听。
Autorun
autorun 是 Reaction 的简单封装。
同步执行
其他的 TFRP 类库,比如 Tracker 和 Knockout ,数据更新后的执行都是异步的,需要等到下一个 event loop 。(可以想象成 setTimeout)
而 Mobx 的执行是同步的,这样做有两个好处:
- ComputedValue 在他依赖的值修改后可以马上被使用,这样你就永远不会使用一个过期的 ComputedValue
- 调试方便,堆栈里没有冗余的 Promise / async 库
Transation
由于 mobx 的更新是同步的,所以每 set 一个值,就会触发 reaction 的更新。所以为了批量更新,就引入了 transation 。
transaction(() => {
user.firstName = "foo";
user.lastName = "bar";
});
在一些情况下,等所有的修改执行完再执行所有的 deviration 会更合适。注意 transaction 只是推迟了 deviration 的执行,本身还是同步的。
Action
action 是 transation 是简单封装,支持通过 decorator 的方式调用。并且是 untrack
的,这样可以在 Derivation 里调用他。
Observe (mobx-react)
第一次 render 时:
- 初始化一个 Reaction,onValidate 时会 forceUpdate Component
- 在 reaction.track 里执行 baseRender,建立依赖关系
有数据修改时:
- 触发 onValidate 方法,执行 forceUpdate
- 触发 render 的执行 (由于在 reaction.track 里执行,所以会重新建立依赖关系)
shouldComponentUpdate:
- 和 PureRenderMixin 类似的实现,阻止不必要的更新
componentWillReact:
- 数据更新的时候触发
- 注意和 componentWillMount 和 componentWillUpdate 的区别
总结
第一眼看 mobx 觉得非常简单,概念也少。这对于简单项目可能够了,但在项目复杂之后就需要用到一些高级的功能,从而需要接触很多的概念,比如 Observable, ComputedValue, Derivation, Action, Transation, Autorun, Reaction, Modifier 等等。其实一点都不比 redux 简单。。
个人很喜欢 mobx 这个库,里面包含很多非常巧妙的实现和优化。所以试着想把原理给讲明白,但写完之后发现还是有些晦涩。
参考
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论