MobX 的响应式原理是怎样的?依赖收集与更新触发机制详解
MobX 是一个基于透明函数响应式编程(TFRP)的状态管理库,核心思想是:任何源自应用状态的东西都应该自动地获得。它通过 Proxy 拦截对象属性的读写操作,在 getter 中收集依赖、在 setter 中触发更新,实现状态变化后所有依赖方自动响应。
响应式原理:依赖收集与触发更新
MobX 的核心机制分两个阶段运作:
依赖收集阶段——当 autorun、reaction 或 computed 首次执行时,函数内部访问了哪些 observable 属性,MobX 就会记录下这些属性与当前函数的依赖关系。具体实现上,每个 observable 属性内部维护一个 observers 集合,每个 derivation(autorun/computed)内部维护一个 observables 集合,两者互相关联。
触发更新阶段——当通过 action 修改 observable 属性时,MobX 遍历该属性的所有 observers,将对应的 derivation 标记为过期并重新执行。
javascriptimport { 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 更新。
javascriptaction(() => { store.firstName = 'Zhang'; store.lastName = 'San'; // 不会触发两次 autorun,而是在 endBatch 时统一触发一次 })();
如果不用 action 直接修改状态,每次赋值都会立即触发更新,可能导致中间状态被响应函数读取,产生不必要的渲染。
Computed 的缓存与懒计算
Computed 不是简单的"派生值",它有两个重要特性:
- 缓存——只有依赖的 observable 变化时才标记为过期,否则直接返回上次计算的缓存值
- 懒计算——如果没有 observer 消费这个 computed,它永远不会执行计算逻辑
内部实现上,computed 同时是 derivation(依赖 observable)和 observable(被其他 derivation 观察),处于依赖链的中间层。
MobX 与 Redux 的核心差异
| 维度 | MobX | Redux |
|---|---|---|
| 更新方式 | 可变状态,直接赋值 | 不可变状态,返回新对象 |
| 订阅机制 | 自动依赖追踪 | 手动 connect/subscribe |
| 样板代码 | 极少 | 较多(action type、reducer、dispatch) |
| 状态结构 | 支持嵌套对象图 | 推荐扁平化 normalized 结构 |
| 时间旅行 | 不原生支持 | 天然支持 |
| 更新粒度 | 属性级别精确更新 | 组件级别浅比较 |
MobX 适合状态结构复杂、嵌套深、追求开发效率的场景;Redux 适合需要严格数据流、时间旅行调试、团队规模大的项目。
面试追问方向
- MobX 如何处理异步 action? 需要用
runInAction包裹异步回调中的状态修改,或者使用flow+ generator 函数 - 为什么 MobX 不建议在 autorun 中做异步操作? 异步回调中的 observable 读取不会被追踪,因为依赖收集是同步完成的
- makeAutoObservable 和 makeObservable 的区别? 前者自动推断成员类型,后者需要显式标注,后者更适合需要精确控制的场景