项目中使用 Mobx 有一段时间了,与 Redux 相比,自己最直观的感受就是避免了 Redux 中大量的样板代码。不需要再去写 action creator, reducer 等,应用的状态直接在 Action 内修改,Mobx 会自动管理依赖的更新和副作用的触发。
由于想看看 Mobx 是如何实现优雅的状态管理,于是花时间研究了下 Mobx 源码。通过这几篇文章记录一些自己的理解,作为以后项目中的参考,避免项目中可能踩到的各种坑。
具体分析源码之前有必要先理清 Mobx 中的几个概念:
transaction
事务的概念大家都不陌生,通常表示一组原子性的操作。Mobx 中的事务用于批量处理 Reaction 的执行,避免不必要的重新计算。Mobx 的事务实现比较简单,使用 startBatch 和 endBatch 来开始和结束一个事务:
1 | function startBatch() { |
可以看到,事务可以嵌套,直到最外层事务结束之后,才会重新执行 Reaction。用一张图来形象地表示事务的概念:

例如,一个 Action 开始和结束时同时伴随着事务的启动和结束,确保 Action 中(可能多次)对状态的修改只触发一次 Reaction 的重新执行。
1 | function startAction() { |
Atom
任何能用于存储应用状态的值在 Mobx 中称为 Atom,它会在「被观察时」和「自身发生变化时」发送通知。BaseAtom 基类定义了实现「可观察」的几个关键属性和方法:
1 | class BaseAtom implements IAtom { |
ObservableValue 正是继承自 BaseAtom。可以看到,reportObserverd 和 reportChanged 分别调用了 reportObserved 和 propagateChanged 两个方法,这正是 Observable 用于「通知被观察」和「通知自身变化」的两个函数。
Atom 可以说是具有「可观察」功能的最小类型,Mobx 也将它作为 API 导出,让用户能够基于它定制一些可观察的数据类型。
Derivation
Derivation 即能够从当前状态「衍生」出来的对象,包括计算值和 Reaction。Mobx 中通过 Derivation 注册响应函数,响应函数中所使用到的 Observable 称为它的依赖,依赖过期时 Derivation 会重新执行,更新依赖。
IDerivation 接口定义的几个重要属性:
1 | interface IDerivation extends IDepTreeNode { |
可以发现,Observable 和 Derivation 是双向关联的,分别持有对方的引用。
Derivation 通过 dependenciesState 属性标记依赖的四种状态:
NOT_TRACKING
在执行之前,或事务之外,或未被观察(计算值)时,所处的状态。此时 Derivation 没有任何关于依赖树的信息。枚举值-1
UP_TO_DATE
表示所有依赖都是最新的,这种状态下不会重新计算。枚举值0
POSSIBLY_STALE
计算值才有的状态,表示深依赖发生了变化,但不能确定浅依赖是否变化,在重新计算之前会检查。枚举值1
STALE
过期状态,即浅依赖发生了变化,Derivation 需要重新计算。枚举值2
源码中经常能见到 lowestState 之类的变量,表示的是「状态最新的观察者所处的状态」。
接下来是几个在源码中随处可见的概念。
invariant
Mobx 从 React 借鉴了 invariant,在条件为 false 时抛出错误:
1 | function invariant(check: boolean, message: string, thing?) { |
还有基于 invariant 的 fail:
1 | function fail(message: string, thing?): never { |
spy, intercept 和 observe
在 Mobx 源码中,经常可以看到为实现 spy, intercept 和 observe 插入的大段代码。
spy 可以监听 Mobx 中发生的所有事件,包括可观察值的变化、Action 的执行、Derivation 的计算等,典型的应用就是 mobx-react-devtools。
典型的实现 spy 的代码:
1 | // 事件开始前 |
intercept 和 observe 可以在 observable 变化前后设置钩子函数。
intercept 可以在 observable 变化前对该变化做出修改,包括取消该变化,例如:
1 | // ObservableValue 变化时 |
observe 会响应所有的变化,即使处在事务中,例如:
1 | // ObservableValue 的 setNewValue 方法 |
简洁起见,接下来几篇文章引用的源码中都会忽略这部分代码。