新闻详情

新闻详情

首页 / 资讯中心 / 详情

@formily/reactive 响应式核心之 reaction:带脏检查的响应式订阅 API 深度解析

发布时间:2026/11/15 5:24:46来源:尧图网络
@formily/reactive 响应式核心之 reaction:带脏检查的响应式订阅 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点击查看免费下载导读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),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

【外设】之大彩串口显示屏 2026/11/15 4:54:16

【外设】之大彩串口显示屏

大彩串口屏初步使用 1 .官网下载 STM32 屏幕 GUI 设计资料 http://www.gz-dc.com/category/typeid/4112 找到 STM32 Keil 工程,移植相关代码因项目而异进行移植,由于项目简单,本人只对用到的指令接口进行修改。 比如:注意事项&…

阅读更多 →
无法下载Windows系统iso文件 2026/11/15 2:04:12

无法下载Windows系统iso文件

当我遇到这个问题的时候,我打开了一个网站: 登录 然后我打算下载的时候: 突然那个官方的连接就可以下载了:

阅读更多 →
【清华代码熊】DeepSeek V4.1 Flash 后训练详解 2026/11/14 10:11:15

【清华代码熊】DeepSeek V4.1 Flash 后训练详解

📌 上期解析了 DeepSeek V4.1 Flash 模型架构改进,本期解析 DeepSeek V4.1 Flash 预训练/后训练技术: 🌟 预训练:45T 文本 多模态混合语料、直接训练 sparse attention(取消 DeepSeek V4 的 dense 冷启动&…

阅读更多 →
Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ... 2026/11/14 17:14:38

Shuffle-R1: Efficient RL framework for Multimodal Large Language Models via Data-centric Dynamic ...

文章主要内容和创新点 主要内容 本文聚焦于多模态大语言模型(MLLM)强化学习(RL)训练中的效率问题,提出了一个名为Shuffle-R1的框架。研究发现,当前RL训练存在两个关键缺陷: 优势值坍缩(Advantage Collapsing):批次中大多数优势值集中在零附近,导致有效梯度信号被淹…

阅读更多 →
PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction 2026/11/14 16:35:50

PRvL: Quantifying the Capabilities and Risks of Large Language Models for PII Redaction

一、文章主要内容总结 本文聚焦于利用大型语言模型(LLMs)实现个人身份信息(PII)脱敏的研究,旨在解决传统脱敏方法(如基于规则的系统、领域特定命名实体识别(NER)模型)泛化能力差、跨格式/跨语境适应性弱的问题。 研究通过全面评估多种LLM架构(包括密集型LLM(D-LLM…

阅读更多 →
LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model 2026/11/15 4:06:36

LLaVA-RE: Binary Image-Text Relevancy Evaluation with Multimodal Large Language Model

文章主要内容和创新点 主要内容 本文聚焦于二进制图像-文本相关性评估任务(判断图像与文本“相关”或“不相关”),针对该任务中文本格式多样、相关性定义随场景变化等挑战,提出了基于多模态大语言模型(MLLM)的解决方案LLaVA-RE。 模型设计:LLaVA-RE基于LLaVA 1.5架构,…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉