Skip to content
目录

@formily/reactive 响应式内核

1. 核心机制图(可口述)

  1. observable/define/model 将对象代理化。
  2. autorun/reaction/Tracker 在读取阶段收集依赖。
  3. 写入阶段触发 runReactionsFromTargetKey 调度。
  4. batch/action/untracked 控制触发边界与收敛行为。

2. 为什么用 Proxy(而不是 defineProperty)

handlers.ts 可见,Formily 响应式完整拦截了:

  • get/set/delete/ownKeys/has
  • Map/Set/WeakMap/WeakSet 的 get/set/add/delete/clear/iterate

这让它能支持:

  • 普通对象与数组;
  • 集合类型;
  • 动态 key 与迭代依赖追踪。

这类能力在大规模动态表单里非常关键(字段集合与路径本身经常变化)。


3. 依赖收集与触发的关键实现点

3.1 收集:读时绑定

  • bindTargetKeyWithCurrentReaction:把当前 reaction 绑定到 target+key。
  • ReactionStack 维护当前追踪上下文。

3.2 触发:写时调度

  • runReactionsFromTargetKey 统一处理 add/set/delete/clear。
  • 数组/集合结构变化会触发额外 iteration 依赖(如 length/ITERATION_KEY)。

3.3 去重与防抖动

  • PendingReactions / PendingScopeReactions 去重批量执行。
  • batchStart/batchEnd 在批处理边界统一 flush。

4. computed 的实现特点(高频)

annotations/computed.ts 的设计重点:

  • computed 本质是一个特殊 reaction(_isComputed)。
  • 采用 _dirty 懒求值策略;依赖变更只标脏,不立即重算。
  • 被其他 reaction 读取时,再触发求值与缓存更新。

收益:

  • 对“频繁写、稀疏读”的场景非常友好;
  • 避免在写时链式计算放大。

5. action / batch / untracked 的职责边界

  • batch:合并多次变更,减少重复调度。
  • actionbatch + untrack,避免写操作过程误收集依赖。
  • untracked:临时关闭依赖收集,用于“只想执行,不想订阅”的逻辑。

在 Formily core 里,这三者被大量用于:

  • 字段初始化;
  • 批量 setFieldState
  • 校验结果写入;
  • schema 编译落状态。

6. React 场景下的资源释放问题

@formily/reactive-reactuseCompatFactory 为了兼容 StrictMode/Concurrent 场景,额外引入了 GarbageCollectorFinalizationRegistry + fallback timer):

  • 正常卸载时主动 dispose
  • 极端场景下兜底回收,避免 reaction 泄漏。

这是“响应式系统 + React 运行时”结合时很实际的工程点。


7. 与 MobX 类方案的关系(常见说法)

Formily reactive 的能力接近轻量 MobX 核心子集,但针对 Formily 场景做了定向优化:

  • 更强调低层可控(可直接与 core 内部调度衔接)。
  • 重点优化路径化字段状态与批量更新。
  • 通过 Tracker 对 React observer 场景做了更薄桥接。

8. 延伸追问:链式 reaction 的边界

社区 PR 中有关于链式响应遗漏与多余触发的讨论与修复:

说明这套系统在“正确性 vs 性能”之间持续微调,属于响应式框架的典型演进轨迹。

MIT License.