前端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点击查看免费下载导读reaction是 Formily 底层响应式引擎formily/reactive提供的高阶订阅 API它接收一个追踪函数tracker与一个回调函数subscriber当追踪函数中读取的 observable 数据变化时自动重新执行追踪但只有当追踪函数的返回值发生变化时才触发回调。本文将以packages/reactive/docs/api/reaction.md为骨架结合 reaction 源码实现 与 测试用例完整讲解其 API 签名、参数语义、脏检查Dirty Check机制、批处理batch协同原理以及在实际业务中的最佳实践。一、reaction 是什么与 autorun 的定位差异在formily/reactive的 API 体系中autorun与reaction都是「订阅-响应」式 API但二者语义不同autorun追踪函数执行即产生副作用只要追踪到的 observable 数据发生变化追踪函数本身就会被重新执行副作用直接写在追踪函数体内。reaction将「数据读取」与「副作用触发」拆分为两个阶段——追踪函数只负责计算并返回一个值回调函数负责消费该值。只有当返回值变化时回调才执行。官方文档对该 API 的描述如下Receive a tracker function and a callback response function. If there is observable data in the tracker, the tracker function will be executed repeatedly when the data changes, but the callback execution must be executed when the tracker function return value changes.即追踪函数中的 observable 数据变化会反复触发追踪函数的重新执行但回调函数只在追踪函数返回值变化时才会被调用。这一设计让reaction天然具备「对计算结果的去重」能力适合用于「数据变化 → 派生状态 → 外部副作用」的链路避免无效的副作用触发。二、API 签名与参数语义2.1 完整签名interface IReactionOptionsT { name?: string equals?: (oldValue: T, newValue: T) boolean //Dirty check fireImmediately?: boolean //Is it triggered by default for the first time, bypassing the dirty check } interface reactionT { ( tracker: () T, subscriber?: (newValue: T, oldValue: T) void, options?: IReactionOptionsT ): void }对应的类型声明位于 packages/reactive/src/types.ts#L86-L90export interface IReactionOptionsT { name?: string equals?: (oldValue: T, newValue: T) boolean fireImmediately?: boolean }2.2 参数详解参数类型说明tracker() T追踪函数。执行期间读取的 observable 数据会被自动收集为依赖Dependency Collection依赖变化时该函数会被重新执行并产出新的返回值subscriber(newValue: T, oldValue: T) void订阅回调。在脏检查通过后触发入参为追踪函数的新旧返回值options.namestring可选reaction 的名称便于调试与追踪默认值为Reaction见源码第 108 行options.equals(oldValue, newValue) boolean可选自定义脏检查函数。返回true表示新值与旧值相等、不触发回调不传时默认使用oldValue ! newValue的浅比较options.fireImmediatelyboolean可选首次创建时是否无条件触发一次回调绕过脏检查。默认false返回值() voiddispose 函数调用后解除该 reaction 的所有依赖绑定停止后续触发2.3 返回值dispose 函数reaction会返回一个 dispose 函数。调用它即调用disposeBindingReactions见 packages/reactive/src/reaction.ts#L174-L178完成两件事将该 reaction 从RawReactionsMap可观察对象属性 → 依赖它的 reaction 集合的映射表中全部删除同时清空其_reactionsSet将其从待执行队列PendingReactions、PendingScopeReactions中移除。从 测试用例 可见dispose 之后无论 observable 数据如何变化回调都不会再被调用const dispose reaction(() { return obs.aa.bb }, handler) obs.aa.bb 123 expect(handler).toBeCalledTimes(0) // 值未变化不触发 obs.aa.bb 111 expect(handler).toBeCalledTimes(1) // 值变化触发 dispose() obs.aa.bb 222 expect(handler).toBeCalledTimes(1) // 已解除订阅不再触发三、核心机制源码解析脏检查与调度链3.1 reaction 的完整实现reaction的实现位于 packages/reactive/src/autorun.ts#L102-L153核心逻辑拆解如下export const reaction T( tracker: () T, subscriber?: (value: T, oldValue: T) void, options?: IReactionOptionsT ) { const realOptions { name: Reaction, ...options, } const value: IValue {} const dirtyCheck () { if (isFn(realOptions.equals)) return !realOptions.equals(value.oldValue, value.currentValue) return value.oldValue ! value.currentValue } const fireAction () { try { //如果untrack的话会导致用户如果在scheduler里同步调用setState影响下次React渲染的依赖收集 batchStart() if (isFn(subscriber)) subscriber(value.currentValue, value.oldValue) } finally { batchEnd() } } const reaction: Reaction () { if (ReactionStack.indexOf(reaction) -1) { releaseBindingReactions(reaction) try { ReactionStack.push(reaction) value.currentValue tracker() } finally { ReactionStack.pop() } } } reaction._scheduler (looping) { looping() if (dirtyCheck()) fireAction() value.oldValue value.currentValue } reaction._name realOptions.name reaction() value.oldValue value.currentValue if (realOptions.fireImmediately) { fireAction() } return () { disposeBindingReactions(reaction) } }3.2 一次完整触发流程首次执行创建时立即执行reaction()。执行期间将自身推入全局ReactionStack随后执行tracker()追踪函数中读取的 observable 属性会通过bindTargetKeyWithCurrentReactionpackages/reactive/src/reaction.ts#L99-L112被记录到RawReactionsMap建立起「属性 → reaction」的反向依赖表。执行结束后把首次结果写入value.oldValue作为脏检查基准。依赖变化observable 属性被修改时runReactionsFromTargetKey会找到所有依赖该属性的 reaction并执行其_schedulerpackages/reactive/src/reaction.ts#L71-L93。重新追踪 脏检查_scheduler内先调用looping()即重新执行追踪函数得到新的value.currentValue随后执行dirtyCheck()——只有新旧返回值不同才调用fireAction()触发回调。最后将value.currentValue更新为value.oldValue为下一次比较做准备。批处理保护fireAction内部使用batchStart()/batchEnd()包裹回调执行确保回调期间产生的 observable 修改不会被立刻递归派发而是合并到当前批处理周期结束后统一执行对应 packages/reactive/src/reaction.ts#L180-L193 的batchStart/batchEnd实现。源码注释也解释了这一点如果不做批处理包裹用户在调度器里同步调用setState会影响下一次 React 渲染的依赖收集。3.3 脏检查Dirty Check脏检查是reaction区别于autorun的灵魂默认浅比较value.oldValue ! value.currentValue。因此「对同一属性重复赋相同值」「ab 整体值不变但组成项互换」这类变化不会触发回调。自定义 equals传入equals后脏检查变为!equals(oldValue, currentValue)即 equals 返回true相等则跳过触发。典型场景是用深比较如JSON.stringify比较过滤「内容等价但引用不同」的对象变化。测试用例 对两种场景均有验证// 浅比较同一值重复赋值不触发 define(obs, { aa: observable.ref }) reaction(() obs.aa, handler) batch(() { obs.aa 123 obs.aa 123 }) expect(handler).toBeCalledTimes(0) // 自定义 deep equals引用不同但内容等价不触发 reaction( () obs.aa, handler, { equals: (a, b) JSON.stringify(a) JSON.stringify(b) } ) obs.aa { bb: 123 } // 与旧值内容相同 expect(handler).toBeCalledTimes(0)四、官方示例逐行解读文档自带的完整示例如下import { observable, reaction, batch } from formily/reactive const obs observable({ aa: 1, bb: 2, }) const dispose reaction(() { return obs.aa obs.bb }, console.log) batch(() { //Wont trigger because the value of obs.aa obs.bb has not changed obs.aa 2 obs.bb 1 }) obs.aa 4 dispose()逐行行为推演observable({ aa: 1, bb: 2 })创建可观察对象reaction(() obs.aa obs.bb, console.log)注册追踪首次执行追踪函数返回3同时收集aa、bb两个依赖由于未传fireImmediately此时console.log不会被调用batch(() { obs.aa 2; obs.bb 1 })批处理内两次赋值aa bb仍然等于3脏检查不通过不触发回调——这正是文档注释 Wont trigger because the value of obs.aa obs.bb has not changed 的含义也体现了「追踪函数返回值变化才触发」的核心语义obs.aa 4aa bb变为6脏检查通过console.log(6, 3)被调用一次dispose()解除订阅后续对aa/bb的修改不再触发任何行为。五、高级用法与边界场景5.1 fireImmediately首帧立即触发默认情况下reaction创建后不会立即执行回调仅执行追踪建立依赖。需要「初始化即消费一次当前值」时传入fireImmediately: true。从源码看首次reaction()执行并同步oldValue之后会绕过脏检查直接调用一次fireAction()。测试用例 验证了该行为const dispose reaction( () obs.aa.bb, handler, { fireImmediately: true } ) expect(handler).toBeCalledTimes(1) // 创建即触发一次 obs.aa.bb 123 expect(handler).toBeCalledTimes(1) // 值未变不触发 obs.aa.bb 111 expect(handler).toBeCalledTimes(2) // 值变化再次触发5.2 回调内的依赖不会被追踪untrack 语义reaction的依赖收集只发生在追踪函数执行期间。在 测试用例 中回调函数内部读取了obs.aa.cc但对obs.aa.cc的修改并不会触发回调——因为回调执行时该 reaction 已不在ReactionStack中不会建立新的依赖绑定。这保证了副作用代码不会反过来「污染」依赖图避免形成意外循环。5.3 与 batch 配合合并多次变更由于batch会让内部的 observable 变更在批处理结束时统一派发相关 API 见 batch 文档reaction在批处理结束后只做一次脏检查。若批处理内多次变更的净效果使返回值不变回调依然不会触发见第四节示例这在大批量数据同步场景中能显著减少副作用执行次数。六、reaction 与 autorun 的对比选型维度autorunreaction副作用位置副作用写在追踪函数体内副作用在独立回调中追踪函数只返回计算值触发条件任一依赖数据变化即触发依赖变化且返回值变化才触发脏检查无天然每次执行有默认浅比较可自定义equals典型场景渲染、日志、同步外部状态派生值监听、防抖副作用、数据过滤去重首帧触发创建时立即执行一次默认不触发需fireImmediately二者共享同一套底层依赖收集与调度机制ReactionStack、RawReactionsMap、releaseBindingReactions实现在同一个文件 packages/reactive/src/autorun.ts 中reaction可以看作在autorun的「立即响应」之上叠加了「结果去重」能力。若需要同时对追踪函数内的局部副作用做缓存管理还可参考autorun.memo/autorun.effect同一文件第 60-100 行它们与reaction可以组合使用。七、实践建议与注意事项追踪函数保持纯计算不要在tracker中写副作用否则返回值可能受副作用干扰脏检查语义被破坏。返回值尽量是原始类型或可控引用默认浅比较下若返回对象字面量每次追踪都会得到新引用脏检查恒通过、回调每次依赖变化都会触发。需要「内容级」去重时使用equals自定义深比较注意深度比较的性能成本。及时 disposereaction返回的 dispose 函数应在组件卸载、页面销毁等时机调用避免内存泄漏与无效触发。优先用 batch 包裹关联变更同一逻辑块内的多次赋值放进batch可让 reaction 在批处理结束时仅做一次脏检查减少回调抖动。在 formily 生态中的定位formily/reactive是整个 FormilyReact/Vue2/Vue3表单引擎的响应式底座reaction这类订阅 API 通常被上层formily/react、formily/vue的 observer 体系间接消费。直接使用它适合在业务侧做「响应式派生值 → 外部副作用」的轻量场景例如把表单状态派生为统计数据、路由参数联动、本地缓存同步等。总结reaction是formily/reactive中「追踪-消费」分离的响应式订阅原语核心价值在于通过脏检查机制过滤无效触发。掌握其三个选项参数name、equals、fireImmediately、dispose 语义以及与batch的协同方式即可在 Formily 的响应式体系中写出精确、高效的副作用逻辑。更多可观测 API如autorun、batch、observable、tracker可继续查阅 packages/reactive/docs/api 目录下的对应文档。赞分享前端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 响应式核心 API 实战解析reaction 的脏检查与依赖订阅机制Formily 响应式核心 API 实战解析reaction 的脏检查与依赖订阅机制 导读 reaction 是 Formily 响应式核心包 formil前端UI组件Formily 核心架构深度解析基于 formily/reactive 领域模型的响应式表单内核Formily 核心架构深度解析基于 formily/reactive 领域模型的响应式表单内核 导读 本文围绕 packages/core/docs/gu前端UI组件Formily 响应式核心 formily/reactive 深度指南安装、快速开始与源码级原理Formily 响应式核心 formily/reactive 深度指南安装、快速开始与源码级原理 formily/reactive 是 Formily 体前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
2026智能家居场景定制分水岭:AI预判与全屋智能落地指南 /* 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 13:17:13
Presto Release 0.90 技术解读:规划器改进、新聚合函数与关键配置项 大数据数据库后端 【免费下载链接】presto The official home of the Presto distributed SQL query engine for big data 项目地址: https://gitcode.com/gh_mirrors/pre/presto 点击查看 免费下载 本文以 Presto 0.90 版本的官方发布说明为主体,逐项解… · 2026/9/24 13:17:13
智能家居全屋组网与布线实战指南:从AC+AP到Mesh /* 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 13:17:00
AI微服务底座向导式安装实战:Ollama+Qdrant+Dify+网关一键部署 我一直觉得,AI 应用开发里最劝退人的环节不是写代码,而是搭环境。你想做一个带知识库问答的智能体,背后要跑模型推理、向量检索、应用编排,再来个 API 网关做统一入口,这一整套微服务底座手动配下来,光依赖… · 2026/9/24 23:07:18
VC如何为AI创业公司挑选云平台:从技术尽调到成本规划 上个月和一个在头部机构做投后的朋友聊项目,他跟我抱怨:Portfolio 里 20 多家 AI 创业公司,今年光是处理算力报销和平台选型就占掉三分之一的工作量。这件事确实被很多人低估了——AI 公司的业务可以千差万别,但底层都长在同一片土… · 2026/9/24 23:07:18
SAP PFCG菜单乱码排查指南:从语言链到文本表修复 最近在权限顾问的群里,又看到有人贴了一张PFCG菜单乱码的截图,底下好几个刚入行的朋友跟着问:是不是权限配错了?是不是角色数据坏了?要不要重启服务器?我一看那截图,心里差不多就有数了——这种… · 2026/9/24 23:07:18
LeetCode 102二叉树层序遍历全解:BFS队列模板与高频变体 LeetCode 102这道“二叉树层序遍历”,在LeetCode上标记为中等难度,却几乎是每一场算法面试的“必考热身题”。如果你刷过LeetCode热门100题,大概率已经见过它;如果你还没开始刷二叉树,这道题作为切入点也再合适不过。层… · 2026/9/24 23:07:18
二叉树层序遍历与BFS:队列原理到LeetCode变体题实战 LeetCode 102 二叉树层序遍历,几乎是每个刷题人绕不开的入门题。题目本身看着很短:给你一棵二叉树,从左到右、从上到下,把每一层的节点值输出到一个二维数组里。但就是这道题,每年都能卡住不少刚开始刷算法的人。你可能… · 2026/9/24 23:07:18
Agent安全实战:从越权到暴走,构建L1-L5分级防护体系 1. 从两起真实越权事件说起:Agent 安全为什么突然成了焦点过去大半年,智能体(Agent)从"能聊两句的玩具"迅速变成了"能自己调工具、自己写文件、自己发请求"的执行体。能力上来了,事故也跟着来了。… · 2026/9/24 23:07:12
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44