@formily/reactive 核心概念深入解析:Observable、Reaction、Computed 与 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 仓库中 formily/reactive 核心概念文档 编写系统讲解该响应式编程模型最核心的四个概念Observable可订阅对象、Reaction订阅者/依赖追踪、Computed可缓存计算结果的 Reaction与Batch更新批处理。formily/reactive是 formily 表单体系的状态管理基石其上层包如formily/core、formily/react、formily/vue的字段联动、校验、值管理全部建立在这套响应式内核之上。读完本文你将掌握如何创建 observable 对象、如何订阅并追踪依赖、如何用 computed 缓存派生数据、以及如何用 batch/action 合并更新从而避免重复渲染。Observable可订阅对象observable 是响应式编程模型中最重要的一块。一个 observable 对象字面意思是可订阅对象的核心行为是我们在每次操作该对象的属性数据的过程中会自动通知订阅者。formily/reactive创建 observable 对象主要通过ES Proxy来实现它可以做到完美劫持数据操作。创建 Observable 的 API 家族在formily/reactive中主要使用以下 API 来创建 observable 对象observable函数创建深度 observable 对象observable.deep函数创建深劫持 observable 对象observable.shallow函数创建浅劫持 observable 对象observable.computed函数创建缓存计算器observable.box函数创建带 get/set 方法的 observable 对象observable.ref函数创建引用级 observable 对象define函数定义 observable 领域模型可以组合observable函数与其静态属性比如observable.computed函数完成领域模型的定义model函数定义自动 observable 领域模型它会将 getter/setter 属性包装为 computed 计算属性将函数包装为 action将其他数据属性用 observable 包装注意这里是深劫持在 observable.ts 的源码中可以看到这些静态属性的挂载方式export function observableT extends object(target: T): T { return createObservable(null, null, target) } observable.box annotations.box observable.ref annotations.ref observable.deep annotations.observable observable.shallow annotations.shallow observable.computed annotations.computed observable[MakeObModelSymbol] annotations.observabledefine 与 model领域模型的定义define允许你精确地为对象的每个属性指定注解annotation例如某个属性用observable.computed声明为计算属性、某个方法用action声明为动作model则是一条快捷路径它会遍历对象属性并自动推导存在 getter 的属性 →observable.computed函数属性 →action其余数据属性 →observable深劫持这一推导逻辑见 model.tsexport function modelTarget extends object any(target: Target): Target { const annotations Object.keys(target || {}).reduce((buf, key) { const descriptor Object.getOwnPropertyDescriptor(target, key) if (descriptor descriptor.get) { buf[key] observable.computed } else if (isFn(target[key])) { buf[key] action } else { buf[key] observable } return buf }, {}) return define(target, annotations) }而define本身model.ts会为对象标记ObModelSymbol、构建数据树并逐个应用注解。这种注解驱动的设计让领域模型既可以用model一键自动化也可以用define精确控制每一个属性的响应式语义。box 的实现细节observable.box创建一个带get()/set()方法的包装对象。从 box.ts 的源码可以看到get中调用bindTargetKeyWithCurrentReaction完成依赖收集set中在值真正发生变化时调用runReactionsFromTargetKey触发依赖派发function get() { bindTargetKeyWithCurrentReaction({ target: store, key, type: get }) return store.value } function set(value: any) { const oldValue store.value store.value value if (oldValue ! value) { runReactionsFromTargetKey({ target: store, key, type: set, oldValue, value }) } }注意set中oldValue ! value的相等性判断——这意味着重复赋相同值时不会触发任何反应这也是响应式性能优化的一个基础细节。Reaction可订阅对象的订阅者reaction 在响应式编程模型中相当于可订阅对象的订阅者。它接收一个tracker 函数这个函数在执行的时候如果函数内部对 observable 对象中的某个属性进行了读操作依赖收集当前 reaction 就会与该属性进行绑定依赖追踪直到该属性在其他地方发生了写操作就会触发 tracker 函数重复执行。从订阅到派发订阅其实是一个封闭的循环状态机每次 tracker 函数执行时都会重新收集依赖依赖变化时又会重新触发 tracker 执行。因此如果一旦不想再订阅 reaction 了一定要手动 dispose否则会内存泄漏——这是使用formily/reactive时必须养成的习惯。创建 Reaction 的 API在formily/reactive中主要使用以下几个 API 来创建 reactionautorun创建一个自动执行的响应器reaction创建一个可以实现脏检查的响应器Tracker创建一个依赖追踪器需要用户手动执行追踪从 autorun.ts 的源码可以看到autorun创建了一个闭包形式的 reaction 并立即执行一次reaction()返回一个 dispose 函数用于解除绑定与清理 effectsexport const autorun (tracker: Reaction, name AutoRun) { const reaction: Reaction () { if (!isFn(tracker)) return if (reaction._boundary 0) return if (ReactionStack.indexOf(reaction) -1) { releaseBindingReactions(reaction) try { batchStart() ReactionStack.push(reaction) tracker() } finally { ReactionStack.pop() reaction._boundary batchEnd() reaction._boundary 0 reaction._memos.cursor 0 reaction._effects.cursor 0 } } } // ... reaction() return () { disposeBindingReactions(reaction) disposeEffects(reaction) cleanRefs() } }reaction带脏检查的响应器与autorun不同reactionautorun.ts接受一个 tracker 与一个 subscriber并通过equals比较新旧值来实现脏检查——只有 tracker 的返回值真正发生变化时subscriber 才会被触发默认比较方式是!也可通过options.equals自定义。它还支持fireImmediately选项决定是否在创建时立即触发一次 subscriberconst dirtyCheck () { if (isFn(realOptions.equals)) return !realOptions.equals(value.oldValue, value.currentValue) return value.oldValue ! value.currentValue }autorun.memo 与 autorun.effect在 autorun 函数体内还可以使用两个进阶能力autorun.tsautorun.memo(callback, dependencies)按依赖数组缓存计算结果依赖未变时直接返回缓存值autorun.effect(callback, dependencies)按依赖数组异步执行副作用内部通过Promise.resolve(0).then(...)延后执行支持返回 dispose 函数进行清理。这两个 API 都必须且只能在 autorun 函数体内部调用否则会抛出错误。Tracker手动依赖追踪Tracker是一个类需要用户手动调用track(tracker)来执行追踪tracker.ts。它通过构造时传入的scheduler调度重跑且当其_boundary 0时执行dispose()。这在高阶场景如 React 的useObserver这类渲染绑定场景中非常有用。Computed可以缓存计算结果的 Reactioncomputed 在响应式编程模型中也是比较重要的概念一句话概括computed 是一个可以缓存计算结果的 Reaction。它的缓存策略是只要 computed 函数内部所依赖的 observable 数据发生变化函数才会重新执行计算否则永远读取缓存结果。这里要求 computed 函数必须是纯函数——内部依赖的数据要么是 observable 数据要么是外部常量数据如果是外部变量数据非 observable那么外部变量发生变化时 computed 是不会重新执行计算的。源码级缓存机制从 computed.ts 的源码可以看到computed 内部维护了一个带_dirty标记的ComputedReaction依赖变化时reaction._scheduler将_dirty置为true并触发对该属性有依赖的其他 reaction读取时若reaction._dirty为true才真正重算reaction()否则直接返回store.value缓存值function get() { if (hasRunningReaction()) { bindComputedReactions(reaction) } if (!isUntracking()) { if (reaction._dirty) { reaction() reaction._dirty false } } else { compute() } bindTargetKeyWithCurrentReaction({ target: context, key: property, type: get }) return store.value }_dirty这个惰性求值标记正是缓存策略的实现本质重算永远是被依赖变化标记、被下一次读取触发的两次重算之间读取到的都是同一份缓存结果。computed 还支持传入{ get, set }形式其set内部会用batchStart/batchEnd包裹。Batch将多次更新合并为一次派发前面讲到formily/reactive是基于 Proxy 劫持实现的响应式编程模型所以任何一个原子操作都会触发 Reaction 执行这样明显会浪费计算资源。例如一个函数内部对多个 observable 属性进行操作import { observable, autorun } from formily/reactive const obs observable({}) const handler () { obs.aa 123 obs.bb 321 } autorun(() { console.log(obs.aa, obs.bb) }) handler()这样会执行3 次打印autorun 默认执行一次加上obs.aa赋值执行一次、obs.bb赋值执行一次。如果原子操作更多执行次数会更多所以推荐使用batch 模式将更新进行合并import { observable, autorun, batch } from formily/reactive const obs observable({}) const handler () { obs.aa 123 obs.bb 321 } autorun(() { console.log(obs.aa, obs.bb) }) batch(() { handler() })当然也可以使用action进行高阶包装import { observable, autorun, action } from formily/reactive const obs observable({}) const handler action.bound(() { obs.aa 123 obs.bb 321 }) autorun(() { console.log(obs.aa, obs.bb) }) handler()最终执行次数就变成2 次了即便 handler 内部的操作再多也仍然是 2 次。batch 与 action 的底层实现batch本身是通过createBoundaryAnnotation(batchStart, batchEnd)创建的边界注解batch.ts并额外提供两个能力batch.scope创建独立作用域的批处理边界batch.endpoint(callback)在批处理计数BatchCount归零时执行回调若当前不在批处理中则立即执行。action的实现则更有意思action.ts——它不只是批处理还在边界内额外调用了untrackStart/untrackEnd也就是说action 在执行期间既合并更新又暂时关闭依赖收集export const action: IAction createBoundaryAnnotation( () { batchStart() untrackStart() }, () { untrackEnd() batchEnd() } )同样action.scope也提供作用域版本。正因为 action 同时具备 batch 与 untrack 双重语义model才会把领域模型中的函数属性自动包装为 action——这保证了业务方法内部无论写多少次属性都只触发一次派生计算与视图更新。总结formily/reactive的核心概念可以概括为一套闭环Observable用 Proxy 劫持数据读写提供observable及其deep/shallow/box/ref/computed静态族系以及define/model领域模型定义能力Reaction通过读操作收集依赖、写操作派发订阅autorun/reaction/Tracker分别满足自动执行、脏检查与手动追踪的需求注意手动 dispose 避免内存泄漏Computed是基于_dirty惰性标记的缓存型 Reaction纯函数依赖是缓存正确性的前提Batch/action通过批处理边界将多次原子更新合并为一次派发action 还额外关闭依赖收集避免不必要的重复计算。这套内核向上支撑了整个 formily 的字段模型与联动引擎理解这四个概念是深入阅读 formily/reactive 全部 API 文档、进而读懂 formily/core 与 formily/react 的前提。赞分享前端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 核心架构深度解析基于 formily/reactive 领域模型的响应式表单内核Formily 核心架构深度解析基于 formily/reactive 领域模型的响应式表单内核 导读 本文围绕 packages/core/docs/gu前端UI组件3 分钟搞定华硕笔记本风扇失控G-Helper 零门槛校准风扇控制曲线3 分钟搞定华硕笔记本风扇失控G Helper 零门槛校准风扇控制曲线 温度只有 39°C风扇却转得像要起飞或者游戏里 GPU 干到 90°C风扇纹丝不桌面应用系统编程Formily 核心架构解析基于 formily/reactive 响应式领域模型的设计原理Formily 核心架构解析基于 formily/reactive 响应式领域模型的设计原理 导读 本文围绕 packages/core/docs/guid前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Arm GIC-v3中断原理及验证(通过kvm-unit-tests)

Arm GIC-v3中断原理及验证(通过kvm-unit-tests)

零、参考连接 gic-v3相关原理可参考https://zhuanlan.zhihu.com/p/520133301 本文主要通过开源测试工具kvm-unit-tests,针对GIC的中断进行一系列验证,这样可以直入中断底层,熟悉整个原理。 kvm-unit-tests官网为kvm-unit-tests / KVM-Unit-Tests GitLab armv8寄存器介绍…

2026/9/23 13:02:44 阅读更多 →
极限学习机ELM回归预测:Matlab实现与调参避坑指南

极限学习机ELM回归预测:Matlab实现与调参避坑指南

简介:这份资源面向机器学习入门者、科研人员及需要快速搭建回归预测模型的学生,提供极限学习机(ELM)在Matlab环境下的完整实现方案。ELM通过随机初始化隐藏层权重、单次求解输出层权重完成训练,相比传统神经网络大幅提…

2026/9/23 13:02:43 阅读更多 →
PaddleSpeech 中的 PANNs 音频分类模型:panns 模块架构解析与训练部署实战

PaddleSpeech 中的 PANNs 音频分类模型:panns 模块架构解析与训练部署实战

PaddleSpeech 中的 PANNs 音频分类模型:panns 模块架构解析与训练部署实战 【免费下载链接】PaddleSpeech Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speake…

2026/9/23 13:02:42 阅读更多 →

最新新闻

Claude会话数据泄漏再敲警钟:用TaoToken统一Key给AI助手加一道门禁

Claude会话数据泄漏再敲警钟:用TaoToken统一Key给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/23 13:39:24 阅读更多 →
武汉门面转让避坑保姆级教程:3个致命报错与修复

武汉门面转让避坑保姆级教程:3个致命报错与修复

武汉门面转让避坑保姆级教程:3个致命报错与修复 盯着屏幕上一片红字的 StackTrace,是不是脑子瞬间炸了?刚接手武汉门面转让的项目,以为只是改改配置,结果一跑起来,异常堆栈像天书一样堆满了控制台,连哪行代码出问题都找不到。别慌,这种“…

2026/9/23 13:39:23 阅读更多 →
DeepSeek V4.1 Flash 实测:MoE 架构越级挑战 Pro,多模态 Agent 部署指南

DeepSeek V4.1 Flash 实测:MoE 架构越级挑战 Pro,多模态 Agent 部署指南

1. 这次 Flash 版本到底动了谁的蛋糕DeepSeek 又发新模型了,这次是 V4.1 Flash。消息出来那天我正蹲在几个开发者群里摸鱼,结果不到半小时,群里全在刷同一句话——“Flash 把 Pro 给干掉了”。说实话,第一反应是不太信的。按常规套…

2026/9/23 13:39:23 阅读更多 →
Akka Streams 的 mapAsyncUnordered 操作符:乱序并发处理与吞吐量优化指南

Akka Streams 的 mapAsyncUnordered 操作符:乱序并发处理与吞吐量优化指南

后端并发编程异步编程 【免费下载链接】akka-core A platform to build and run apps that are elastic, agile, and resilient. SDK, libraries, and hosted environments. 项目地址: https://gitcode.com/gh_mirrors/ak/akka-core 点击查看 免费下载 导读 mapAs…

2026/9/23 13:39:23 阅读更多 →
autocad破解2026最新

autocad破解2026最新

别乱下补丁了,Autodesk官方授权避坑指南 配置环境就卡半天,这大概是每个刚入行做BIM或CAD建模的兄弟都经历过的噩梦。你从网上随便找个“Autocad破解”包,下载下来,双击安装,结果要么蓝屏,要么激活失败,要么装完打开全是乱码。这…

2026/9/23 13:39:23 阅读更多 →
Prisma Binding 代码生成(Codegen)实战指南:从 `prisma-binding` CLI 到自动化生成工作流

Prisma Binding 代码生成(Codegen)实战指南:从 `prisma-binding` CLI 到自动化生成工作流

Prisma Binding 代码生成(Codegen)实战指南:从 prisma-binding CLI 到自动化生成工作流 【免费下载链接】prisma1 💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated] 项目地…

2026/9/23 13:38:21 阅读更多 →

日新闻

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A…

2026/9/23 0:00:23 阅读更多 →
2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我 刚把开发环境的显示器从1080P换到2K,跑老项目直接报错,版本升级后 API…

2026/9/23 0:01:25 阅读更多 →
3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点 官方文档翻了三遍还是云里雾里?别急,美眉图在实战项目中常被用来做数据可视化,但它的原理比你想的简单。今天咱们直接上手,用一个完整的小项目把美眉图跑通,不再死磕那些冗长的理论说明。…

2026/9/23 0:01:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →