首页/新闻资讯/正文详情

深入理解 @formily/reactive 的 untracked:在响应式追踪中精确隔离依赖收集

发布时间:2026/9/23 23:30:32 来源:云帆数科 栏目:资讯中心
深入理解 @formily/reactive 的 untracked:在响应式追踪中精确隔离依赖收集
前端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点击查看免费下载untracked是formily/reactive响应式核心暴露的一组 API与batch、autorun、reaction并列其作用是在一段给定函数执行期间临时关闭依赖收集函数内部无论读取多少响应式数据都不会被当前正在运行的响应式上下文如autorun、reaction或组件渲染注册为依赖。本文以 untracked.zh-CN.md 为骨架结合源码实现与测试用例讲清它的签名、用法、底层拦截原理以及与batch的本质区别帮助你写出更精确、可预测的响应式逻辑。untracked 是什么API 定位与签名在 Formily 的响应式体系packages/reactive中observable负责把数据变成可追踪对象autorun/reaction负责建立读取即依赖的自动订阅关系。但实际业务里经常出现这类诉求我确实要读取某个响应式值但我不想因为它而变化而触发当前 effect 重跑——例如在 effect 里读一次配置快照、写日志、或把值传给某个不关心变化的第三方库。untracked就是为这种场景准备的。官方文档给出的签名如下interface untrackedT extends () any { (untracker?: T): ReturnTypeT }要点解读入参untracker是一个可选函数函数内部读取的响应式数据不会被收集为依赖返回值类型与入参函数返回值一致ReturnTypeT因此它天然适合包裹有返回值的表达式而不只是过程语句参数可省略untracked()此时仅起到切换开关作用不做任何计算。从源码看untracked的实现极简它由createBoundaryFunction配合两个反应期钩子构成见 untracked.tsimport { createBoundaryFunction } from ./internals import { untrackStart, untrackEnd } from ./reaction export const untracked createBoundaryFunction(untrackStart, untrackEnd)整个 API 的语义可以概括为一句话在给定的untracker函数内部永远不会被依赖收集与batch的用法相似但作用不同下文详述。快速上手从文档用例出发文档给出了最典型的使用场景在autorun中读取响应式值但希望这个读取不影响订阅关系。import { observable, autorun, untracked } from formily/reactive const obs observable({ aa: 11, }) autorun(() { console.log(untracked(() obs.aa)) //变化时不会触发 }) obs.aa 22执行结果分析autorun首次执行时外层函数体读取了untracked(() obs.aa)的返回值由于读取发生在untracked内部obs.aa的依赖绑定被跳过因此autorun并没有订阅aa当obs.aa 22修改数据并触发响应式通知时该autorun不在aa的依赖集合中因此不会重跑控制台只会打印一次11。对应的单元测试见 untracked.spec.ts其中用jest.fn精确验证了调用次数恒为 1这一行为test(basic untracked, () { const obs observableany({}) const fn jest.fn() autorun(() { untracked(() { fn(obs.value) }) }) expect(fn).toBeCalledTimes(1) obs.value 123 expect(fn).toBeCalledTimes(1) })原理剖析UntrackCount 与依赖收集的拦截要理解untracked为什么能屏蔽依赖收集需要追踪三个关键环节状态计数、边界函数、绑定拦截。第一步状态计数开关untrackStart/untrackEnd的实现位于 reaction.tsexport const untrackStart () { UntrackCount.value } export const untrackEnd () { UntrackCount.value-- }它们操作的UntrackCount是 environment.ts 中定义的一个模块级计数器export const UntrackCount { value: 0 }配套的判定函数export const isUntracking () UntrackCount.value 0也就是说untracked内部会把UntrackCount从 0 变成 1支持嵌套叠加从而让整个系统进入屏蔽收集模式。第二步边界函数保证成对执行createBoundaryFunction位于 internals.ts它的职责是保证start/end在任意情况下都成对调用即使回调抛异常finally也会执行endexport const createBoundaryFunction ( start: (...args: any) void, end: (...args: any) void ) { function boundaryF extends (...args: any) any(fn?: F): ReturnTypeF { let results: ReturnTypeF try { start() if (isFn(fn)) { results fn() } } finally { end() } return results } boundary.bound createBindFunction(boundary) return boundary }注意两个细节只有当传入fn是函数时才执行它所以untracked()无参调用是合法的测试no params untracked专门覆盖了这一点返回的boundary函数还挂载了bound方法由 createBindFunction 生成用于在指定context下执行回调方便把类方法直接包进untracked边界内。第三步在绑定入口处拦截依赖收集的真正入口是bindTargetKeyWithCurrentReactionreaction.ts。每当代理对象上的属性被读取baseHandlers/collectionHandlers都会调用它把当前栈顶的 reaction与目标对象的某个 key关联起来export const bindTargetKeyWithCurrentReaction (operation: IOperation) { let { key, type, target } operation if (type iterate) { key ITERATION_KEY } const reactionLen ReactionStack.length if (reactionLen 0) return const current ReactionStack[reactionLen - 1] if (isUntracking()) return // ← untracked 的拦截点 if (current) { DependencyCollected.value true addReactionsMapToReaction(current, addRawReactionsMap(target, key, current)) } }关键就在if (isUntracking()) return这一行即便此时ReactionStack栈顶确实存在正在执行的autorun/reaction说明读取确实发生在响应式上下文里只要UntrackCount 0就直接放弃注册。于是RawReactionsMap中不会新增target.key → reaction的映射后续数据变更触发runReactions时自然也找不到该 reactioneffect 便不会被唤醒。这也解释了文档中用法与 batch 相似的说法两者都只是包一层边界函数batch走batchStart/batchEnd形态相似但干预的环节完全不同见下节。untracked 与 batch 的异同batch的边界实现同样基于createBoundaryFunction只是传入的是batchStart/batchEndreaction.ts。二者的对比如下维度batchuntracked核心计数器BatchCountUntrackCount干预环节通知执行阶段批量收集PendingReactions在batchEnd时统一执行依赖收集阶段isUntracking()为真时直接跳过绑定影响面延迟/合并 effect 触发减少重复执行完全阻断依赖注册effect 不再订阅这些数据典型场景一次性修改多个字段避免中间态触发多次 effect读取但不订阅读取结果变化不应影响当前 effect组合使用可嵌套可嵌套计数器叠加简单记忆batch管什么时候触发untracked管要不要订阅。值得留意的是二者在实现上是正交的runReactions在执行通知时会把UntrackCount临时归零再恢复reaction.ts确保批量派发 effect 时不会误伤其内部正常的依赖收集batchEnd清空待执行队列时也有类似处理。这说明框架刻意保持了两种边界的隔离与可组合性。边界场景与使用要点在 computed 上使用 untrackedobservable.computed的 getter 同样受依赖收集约束。当你想计算出一个值但不希望它内部读取的依赖影响外层订阅时可以像测试 annotations.spec.ts 那样包裹test(computed no track get, () { const obs observable({ aa: 123 }) const compu observable.computed({ get: () obs.aa }) untracked(() { expect(compu.value).toBe(123) }) })在这个用例中untracked内部读取compu.value只是取回计算结果而不会因为这次读取而让外层上下文订阅compu及其依赖的obs.aa。嵌套与无参调用由于UntrackCount是累加计数器untracked支持嵌套由于createBoundaryFunction仅在传入函数时才执行untracked()可作为纯开关使用测试no params untracked保证其可空调用且不抛错。与 autorun / reaction 的配合autorun的执行体位于ReactionStack中autorun.tsreaction的 tracker 同样入栈。因此在autorun内部用untracked包裹的读取 → 不订阅在reaction的 tracker 内部同理在untracked内部调用autorun/reaction其内部的依赖收集不受影响各自拥有独立边界这与batch的行为一致。使用注意它屏蔽的是本次读取与当前 reaction 的绑定并不影响数据本身的可观察性——其他 effect 照常订阅不要在untracked回调内做既想读取又想订阅的操作语义上二者互斥若回调内部抛错finally会保证untrackEnd执行计数器不会泄漏但错误会照常向外传播。结语untracked是formily/reactive中体积最小、语义却最清晰的 API 之一一行源码定义untracked.ts配合反应期计数与绑定入口的拦截reaction.ts、reaction.ts即可精确控制依赖收集的开关。理解它之后你再面对读配置不订阅取快照不重跑这类需求时就有了标准答案配合 batch 使用则能同时控制订阅关系与触发时机两个维度写出更符合预期、更容易推理的响应式代码。赞分享前端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 之 action掌握批量更新与依赖追踪隔离的响应式编程利器Formily Reactive 之 action掌握批量更新与依赖追踪隔离的响应式编程利器 导读 action 是 Formily 响应式核心 formi前端UI组件Formily Reactive 响应式核心autorun 依赖追踪 API 全面解析与源码级实战指南Formily Reactive 响应式核心autorun 依赖追踪 API 全面解析与源码级实战指南 导读 formily/reactive 是 Form前端UI组件Formily Reactive Tracker 深入解析React/Vue 手动依赖追踪器的工作原理与实战Formily Reactive Tracker 深入解析React/Vue 手动依赖追踪器的工作原理与实战 本文围绕 formily/reactive 中前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

AI游戏平台分账系统实战:从技术选型到风控设计
AI游戏平台分账系统实战:从技术选型到风控设计

1. 这不是一篇“教你怎么赚钱”的速成指南,而是一份AI游戏平台创业者的实操复盘最近刷到不少标题扎眼的短视频和公众号推文:“0代码做游戏”“AI生成用户投稿月入10万”“下一个Roblox就在你手机里”。点进去一看,主角往往是某个刚起步的AI U… · 2026/9/23 23:30:20

Java中摩尔气体常数R的正确姿势:常量定义与物理计算实战
Java中摩尔气体常数R的正确姿势:常量定义与物理计算实战

一开始看到“java摩尔常量”这个搜索词的时候,我都有点愣住了。Java和摩尔气体常数,这俩怎么凑一块儿了?后来一想,问这类问题的多半是刚学编程的朋友——要么在做某道物理计算题,题目里出现了R;要么是在一段… · 2026/9/23 23:30:08

MATLAB中的δ符号:含义辨析、输入技巧与工程实战指南
MATLAB中的δ符号:含义辨析、输入技巧与工程实战指南

你有没有碰到过这种场景:打开别人写的MATLAB脚本,翻到某一处,看到一句delta y(2:end) - y(1:end-1);,当场愣住——“这个delta到底代表什么?为什么图例里还能打出δ这种符号?我自己想打怎么打不出来&#… · 2026/9/23 23:30:08

【2026年华为杯D题】山区洪涝灾害下无人机运输与通信协同优化(思路、代码、论文,持续更新)
【2026年华为杯D题】山区洪涝灾害下无人机运输与通信协同优化(思路、代码、论文,持续更新)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 &#x1f381… · 2026/9/24 1:49:50

SNR Wall:认知无线电为什么有灵敏度极限
SNR Wall:认知无线电为什么有灵敏度极限

SNR Wall:认知无线电为什么有灵敏度极限认知无线电的核心承诺是"见缝插针":先感知频谱空洞,再借用它通信。但这条路上横着一堵看不见的墙——不管你把信号放大多少倍,检测器都可能永远看不见它。一、背景与痛点 认知无线… · 2026/9/24 1:49:37

Cadence OrCAD Capture CIS原理图连线与跨页连接实战避坑指南
Cadence OrCAD Capture CIS原理图连线与跨页连接实战避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 1:49:37

Jetson Nano Docker GPU加速实战:从运行时配置到CUDA容器迁移
Jetson Nano Docker GPU加速实战:从运行时配置到CUDA容器迁移

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 1:48:42

CAN分析仪软件选型指南:CANTest、ZCANPro、USB-CAN Tool实测对比
CAN分析仪软件选型指南:CANTest、ZCANPro、USB-CAN Tool实测对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 1:48:42

高刷多屏下显卡待机功耗异常的四层根因与实操优化
高刷多屏下显卡待机功耗异常的四层根因与实操优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 1:46:13

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码