- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
导读
Tracker是 Formily 响应式核心库@formily/reactive提供的一个手动依赖追踪工具,它的设计目标是:在依赖发生变化时不重复执行 tracker 函数,只触发 scheduler,是否需要重新执行由使用者自行决定。这一特性使它成为 React/Vue 等框架适配层连接响应式内核与组件渲染的桥梁——例如@formily/reactive-react的useObserver正是基于Tracker实现组件级依赖追踪与按需重渲染。读完本文,你将掌握Tracker的完整 API 签名、构造函数参数含义、与autorun的本质区别,以及它在真实框架适配中的落地方式。
一、Tracker 是什么
在 packages/reactive/docs/api/tracker.zh-CN.md 中,官方对Tracker的描述非常精炼:
主要用于接入 React/Vue 的手动追踪依赖工具,在依赖发生变化时不会重复执行 tracker 函数,需要用户手动重复执行,只会触发 scheduler。
拆解这句话,可以得到三个关键结论:
- 手动追踪:与
autorun(自动运行)不同,Tracker不会自动重复执行被追踪的函数,首次执行需要你手动调用track。 - 变化只触发 scheduler:当被追踪的响应式依赖发生变化时,
Tracker只调用构造函数传入的scheduler回调,而不会自动重新执行被追踪的视图函数。 - 面向框架适配:正因为它把"依赖变化"和"重新执行"解耦,上层框架可以在 scheduler 中决定"何时、以何种方式"刷新(比如 React 的
setState/forceUpdate,Vue 的依赖更新调度)。
Tracker定义在 packages/reactive/src/tracker.ts,并从 packages/reactive/src/index.ts 通过export * from './tracker'对外导出,使用时直接import { Tracker } from '@formily/reactive'即可。
二、API 签名与参数说明
官方文档给出的类签名如下:
class Tracker { constructor(scheduler?: (reaction: this['track']) => void, name?: string) track: <T>(tracker?: () => T) => T dispose: () => void }对照源码 packages/reactive/src/tracker.ts 的实现,可以对每个成员做更精确的解读:
1.constructor(scheduler?, name?)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
scheduler | (reaction: Reaction) => void | 无 | 依赖变化时触发的调度函数,收到一个reaction参数(即track方法本身),通常在里面再次调用tracker.track(view)完成重执行 |
name | string | 'TrackerReaction' | 该 Tracker 反应的名字,主要用于调试与标识 |
注意签名中reaction: this['track']的写法——scheduler 收到的参数就是当前 Tracker 实例的track方法本身。这一点在源码中体现为:
constructor( scheduler?: (reaction: Reaction) => void, name = 'TrackerReaction' ) { this.track._scheduler = (callback) => { if (this.track._boundary === 0) this.dispose() if (isFn(callback)) scheduler(callback) } this.track._name = name this.track._boundary = 0 }这里有一个容易被忽略的重要行为:当_boundary === 0(即没有嵌套调用)时,scheduler 触发会先自动dispose()当前 Tracker。也就是说,每次依赖变化触发调度时,旧的依赖绑定会被清理,为接下来重新track收集新依赖做准备。这正是Tracker能实现"依赖动态更新"(见下文测试用例)的底层机制。
2.track(tracker?)
track是被追踪函数执行入口,也是源码中的核心逻辑:
track: Reaction = (tracker: Reaction) => { if (!isFn(tracker)) return this.results if (this.track._boundary > 0) return if (ReactionStack.indexOf(this.track) === -1) { releaseBindingReactions(this.track) try { batchStart() ReactionStack.push(this.track) this.results = tracker() } finally { ReactionStack.pop() this.track._boundary++ batchEnd() this.track._boundary = 0 } } return this.results }关键行为逐条说明:
- 非函数直接返回上次结果:
if (!isFn(tracker)) return this.results,isFn定义在 packages/reactive/src/checkers.ts。 - 重入保护:
_boundary > 0时直接返回,避免递归重复执行;同时检查ReactionStack中是否已有自身,防止重复入栈。 - 先释放再收集:执行前调用
releaseBindingReactions(this.track)清空上一次的依赖绑定,然后ReactionStack.push(this.track)将自身压入全局反应栈(定义在 packages/reactive/src/environment.ts),随后执行tracker()。执行期间,任何被读取的响应式属性都会通过bindTargetKeyWithCurrentReaction(见 packages/reactive/src/reaction.ts)把当前 Tracker 绑定到该属性上。 - 批处理包裹:整个执行过程用
batchStart()/batchEnd()(同样来自 packages/reactive/src/reaction.ts)包裹,保证执行过程中触发的多次依赖变更被合并到PendingReactions,在批处理结束后统一调度。
3.dispose()
dispose = () => { disposeBindingReactions(this.track) }调用disposeBindingReactions(packages/reactive/src/reaction.ts 中定义)将 Tracker 标记为_disposed = true,并释放所有已收集的依赖绑定,同时挂起相关的 computed 反应。销毁后该 Tracker 不再响应任何依赖变化,组件卸载时必须调用它来防止内存泄漏。
三、官方用例逐步解读
原文档给出了一个完整用例:
import { observable, Tracker } from '@formily/reactive' const obs = observable({ aa: 11, }) const view = () => { console.log(obs.aa) } const tracker = new Tracker(() => { tracker.track(view) }) tracker.track(view) obs.aa = 22 tracker.dispose()逐步分析执行过程:
observable({ aa: 11 })创建响应式对象。view读取obs.aa,是待追踪的视图函数。new Tracker(() => { tracker.track(view) })创建 Tracker,scheduler 内部通过闭包再次执行track。注意这里用到了"先声明变量、后引用"的闭包技巧——scheduler 只在依赖变化时才执行,此时tracker变量已完成初始化。tracker.track(view)手动执行第一次追踪:收集obs.aa到当前 Tracker 的依赖集合,view执行打印11。obs.aa = 22修改依赖属性,触发runReactions(packages/reactive/src/reaction.ts),找到绑定在该属性上的 Tracker 并调用其_scheduler,于是tracker.track(view)再次执行,view打印22。关键在于:view的第二次执行是由 scheduler 手动触发的,而不是 Tracker 自动完成的。tracker.dispose()释放所有依赖绑定,此后obs.aa再变化也不会触发任何调度。
这个流程直观展示了"手动追踪 + 手动重执行"的核心模型。
四、结合源码与测试的深入理解
1. Tracker 与 autorun 的本质区别
autorun定义在 packages/reactive/src/autorun.ts,它的实现与Tracker非常相似(同样的ReactionStack压栈、batchStart/batchEnd包裹),但有一个根本差异:
autorun在创建时立即执行一次reaction(),并且把reaction自身作为_scheduler的兜底——依赖变化时 reaction 会被重新调度执行(packages/reactive/src/reaction.ts 的runReactions中,没有_scheduler或不在批处理范围内时直接调用reaction())。Tracker创建时不执行任何函数,首次执行必须手动track;且它强制要求传入 scheduler 来决定"变化后做什么"。
一句话总结:autorun是"自动循环",Tracker是"手动开关"。
2. 测试用例验证的关键行为
packages/reactive/src/tests/tracker.spec.ts 中有 4 个测试,覆盖了 Tracker 最重要的行为:
- base tracker:验证基本流程——
track首次执行、obs.value = 123后 scheduler 触发重新track、dispose后不再响应。 - nested tracker:验证
view内部同时存在读与写(obs.value = obs.value || 321)时,首次track执行一次、依赖变化后经 scheduler 再执行一次,且能拿到正确值。 - tracker recollect dependencies:这是最有价值的一个用例,验证依赖动态重收集:
const view = () => { fn() if (obs.aa === 'aaa') { return obs.bb } return obs.cc }首次track时obs.aa === 'aaa',只收集了obs.aa和obs.bb;随后obs.aa = '111'触发重执行后,view分支改变,重新收集obs.aa和obs.cc;此时再改obs.bb,因为obs.bb已不在依赖集合中,不会触发调度。这就是track执行前releaseBindingReactions先清空旧依赖的意义所在——依赖集合永远是"最近一次执行实际读取的属性"。
- shared scheduler with multi tracker:模拟 React StrictMode 场景——两个 Tracker 共享一个 render 流程,
obs.value变化后只有scheduler1被调用、scheduler2被调用 0 次。这验证了批处理与去重机制:同一轮变化中,已在调度队列里的反应不会重复入队。
3. Tracker 在 React 适配层的真实落地
Tracker并非一个孤立概念,它是 packages/reactive-react/src/hooks/useObserver.ts 中useObserver的底层实现:
export const useObserver = <T extends () => any>( view: T, options?: IObserverOptions ): ReturnType<T> => { const forceUpdate = useForceUpdate() const tracker = useCompatFactory( () => new Tracker(() => { if (typeof options?.scheduler === 'function') { options.scheduler(forceUpdate) } else { forceUpdate() } }, options?.displayName) ) return tracker.track(view) }可以看到:
- 用
useCompatFactory保证组件生命周期内 Tracker 实例只创建一次; - scheduler 中调用
forceUpdate()触发 React 重渲染——这正是"依赖变化只触发 scheduler,由 scheduler 决定重执行"这一设计在框架层的直接体现; options.displayName被透传为 Tracker 的 name,便于调试。
也就是说,你在 React 中使用@formily/react时,每个组件的响应式依赖收集与重渲染调度,底层都是通过Tracker完成的。类似地,@formily/reactive-vue等 Vue 适配层也采用了同一套 Tracker 机制接入 Vue 的渲染调度。
五、最佳实践与注意事项
基于源码与测试,使用Tracker时有几点值得注意:
- 首次执行必须手动调用
track:Tracker 构造函数不会执行任何追踪函数,忘记调用track会导致依赖从未被收集。 - scheduler 中务必重新
track:如果 scheduler 只做"通知"而不重新执行track,那么视图永远不会用新值更新;官方用例中的tracker.track(view)模式是最标准的写法。 - 及时
dispose:组件卸载或 Tracker 不再需要时调用dispose(),释放依赖绑定,避免无效调度与内存泄漏。从源码看,每次 scheduler 触发前也会先dispose旧绑定,但显式调用仍是必须的收尾动作。 - 循环依赖需自控:由于 scheduler 由用户编写,务必保证 scheduler 内部的
track不会造成无限递归(_boundary重入保护只能防住同步重入,无法替代业务层面的终止条件)。 - 嵌套 Tracker 安全:
ReactionStack的存在保证嵌套track时依赖绑定到正确的反应上,测试用例nested tracker已验证该场景。
结语
Tracker是 Formily 响应式体系中最贴近"框架适配"层面的基础工具:它通过"手动追踪、变化只触发 scheduler"的设计,把依赖收集与执行调度彻底解耦,从而让 React/Vue 等不同渲染模型都能以统一方式接入@formily/reactive。理解它,就理解了 Formily 响应式内核如何与前端框架握手。进一步探索时,可以对照阅读 packages/reactive/src/tracker.ts 源码、packages/reactive/src/tests/tracker.spec.ts 测试,以及 packages/reactive/docs/api/autorun.zh-CN.md 中 autorun 的对比实现。
- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
相关推荐
Formily Reactive 响应式核心:autorun 依赖追踪 API 全面解析与源码级实战指南
Formily Reactive 响应式核心:autorun 依赖追踪 API 全面解析与源码级实战指南 导读 @formily/reactive 是 Form
前端UI组件Formily Reactive 之 action:掌握批量更新与依赖追踪隔离的响应式编程利器
Formily Reactive 之 action:掌握批量更新与依赖追踪隔离的响应式编程利器 导读 action 是 Formily 响应式核心 @formi
前端UI组件Formily 核心架构解析:基于 @formily/reactive 响应式领域模型的设计原理
Formily 核心架构解析:基于 @formily/reactive 响应式领域模型的设计原理 导读 本文围绕 packages/core/docs/guid
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考