5月27日 23:31

MobX 的响应式原理是怎样的?依赖收集与更新触发机制详解

MobX 是一个基于透明函数响应式编程(TFRP)的状态管理库,核心思想是:任何源自应用状态的东西都应该自动地获得。它通过 Proxy 拦截对象属性的读写操作,在 getter 中收集依赖、在 setter 中触发更新,实现状态变化后所有依赖方自动响应。

响应式原理:依赖收集与触发更新

MobX 的核心机制分两个阶段运作:

依赖收集阶段——当 autorunreactioncomputed 首次执行时,函数内部访问了哪些 observable 属性,MobX 就会记录下这些属性与当前函数的依赖关系。具体实现上,每个 observable 属性内部维护一个 observers 集合,每个 derivation(autorun/computed)内部维护一个 observables 集合,两者互相关联。

触发更新阶段——当通过 action 修改 observable 属性时,MobX 遍历该属性的所有 observers,将对应的 derivation 标记为过期并重新执行。

javascript
import { observable, autorun, action } from 'mobx'; const store = observable({ count: 0, }); autorun(() => { console.log('count 变化了:', store.count); // 首次执行时收集到 count 依赖 }); action(() => { store.count++; // 触发 setter → 通知所有 observers → autorun 重新执行 })();

关键点:autorun 回调在初始化时会同步执行一次,正是这次执行完成了依赖收集。如果回调中没有读取任何 observable 属性,则不会建立任何依赖关系。

Observable 的底层实现

MobX 6 使用 Proxy 对对象进行深度代理。对于基本类型值,则通过 Atom 类包装:

  • 对象/数组:通过 Proxy 的 get 拦截器调用 reportObserved() 记录当前正在执行的 derivation;通过 set 拦截器调用 reportChanged() 通知所有观察者
  • 基本类型:通过 observable.box() 包装为带 get/set 方法的盒子对象,内部同样基于 Atom 实现
  • Atom 类:是 MobX 响应式系统的最小单元,提供 reportObserved()reportChanged() 两个核心方法
javascript
// 简化版 Atom 原理 class Atom { observers = new Set(); reportObserved() { if (currentlyTracking) { this.observers.add(currentTrackingDerivation); currentTrackingDerivation.addObservable(this); } } reportChanged() { this.observers.forEach(fn => fn.run()); } }

Action 与事务机制

Action 不仅仅是"修改状态的方式",它还承担着事务批处理的职责。MobX 在 action 执行前调用 startBatch(),执行后调用 endBatch(),确保一个 action 中多次修改状态只触发一次 derivation 更新。

javascript
action(() => { store.firstName = 'Zhang'; store.lastName = 'San'; // 不会触发两次 autorun,而是在 endBatch 时统一触发一次 })();

如果不用 action 直接修改状态,每次赋值都会立即触发更新,可能导致中间状态被响应函数读取,产生不必要的渲染。

Computed 的缓存与懒计算

Computed 不是简单的"派生值",它有两个重要特性:

  1. 缓存——只有依赖的 observable 变化时才标记为过期,否则直接返回上次计算的缓存值
  2. 懒计算——如果没有 observer 消费这个 computed,它永远不会执行计算逻辑

内部实现上,computed 同时是 derivation(依赖 observable)和 observable(被其他 derivation 观察),处于依赖链的中间层。

MobX 与 Redux 的核心差异

维度MobXRedux
更新方式可变状态,直接赋值不可变状态,返回新对象
订阅机制自动依赖追踪手动 connect/subscribe
样板代码极少较多(action type、reducer、dispatch)
状态结构支持嵌套对象图推荐扁平化 normalized 结构
时间旅行不原生支持天然支持
更新粒度属性级别精确更新组件级别浅比较

MobX 适合状态结构复杂、嵌套深、追求开发效率的场景;Redux 适合需要严格数据流、时间旅行调试、团队规模大的项目。

面试追问方向

  • MobX 如何处理异步 action? 需要用 runInAction 包裹异步回调中的状态修改,或者使用 flow + generator 函数
  • 为什么 MobX 不建议在 autorun 中做异步操作? 异步回调中的 observable 读取不会被追踪,因为依赖收集是同步完成的
  • makeAutoObservable 和 makeObservable 的区别? 前者自动推断成员类型,后者需要显式标注,后者更适合需要精确控制的场景
标签:Mobx