ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Formily Reactive Tracker 详解:手动追踪依赖的响应式核心工具

Formily Reactive Tracker 详解:手动追踪依赖的响应式核心工具 前端UI组件【免费下载链接】formily Cross Device High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3项目地址https://gitcode.com/gh_mirrors/fo/formily点击查看免费下载导读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/forceUpdateVue 的依赖更新调度。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)完成重执行namestringTrackerReaction该 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.resultsisFn定义在 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) }调用disposeBindingReactionspackages/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) })创建 Trackerscheduler 内部通过闭包再次执行track。注意这里用到了先声明变量、后引用的闭包技巧——scheduler 只在依赖变化时才执行此时tracker变量已完成初始化。tracker.track(view)手动执行第一次追踪收集obs.aa到当前 Tracker 的依赖集合view执行打印11。obs.aa 22修改依赖属性触发runReactionspackages/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 ): ReturnTypeT { 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时有几点值得注意首次执行必须手动调用trackTracker 构造函数不会执行任何追踪函数忘记调用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项目地址https://gitcode.com/gh_mirrors/fo/formily点击查看免费下载相关推荐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),仅供参考
返回列表