尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

@formily/reactive 响应式内核解读:Formily 为什么重新造一个基于 Proxy 的响应式轮子

@formily/reactive 响应式内核解读:Formily 为什么重新造一个基于 Proxy 的响应式轮子 前端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的设计动机展开从 MobX 的四个能力缺口出发解释 Formily 为何在表单场景下必须重新实现一套基于 ES Proxy 的响应式编程模型并结合本仓库源码observable.ts、autorun.ts、reaction.ts、batch.ts深入剖析其核心概念与工程实践。读完你将理解 Observable / Reaction / Computed / Batch 四类原语在 Formily 中的定位、与 MobX 的取舍差异以及如何在业务代码中写出高性能且不泄漏内存的响应式逻辑。一、为什么要重新造轮子四个能力缺口formily/reactivepackage.json 中版本为 2.3.7MIT 协议是 Formily 的响应式内核它的核心思想参考了 MobX但又刻意选择了“重新造轮子”。官方文档docs/guide/index.zh-CN.md给出了 4 点直接原因这四点全部指向表单场景下的特殊需求而非单纯的“重复造轮子”MobX 不支持在 action 内部进行依赖收集。表单联动中一个联动逻辑往往在修改多个字段值的同时又要读取其他字段值来计算新值读操作依赖收集与写操作派发更新交织在同一段代码里。若 action 内不能收集依赖联动依赖就无法被正确追踪而这恰是 Formily 联动表达式频繁出现的模式。MobX 的 observable 函数不支持过滤 React Node、moment、Immutable 之类的特殊对象。表单数据中经常混入日期对象moment/dayjs、不可变数据结构、甚至 JSX 片段如自定义渲染节点如果响应式代理把这些对象也深度劫持会产生不必要的开销甚至行为异常。Formily 需要一套可配置的“哪些对象不该被劫持”的策略。MobX 的 observable 函数会自动把函数变成 action。这在表单场景中会带来隐式副作用字段校验、联动处理器等函数一旦被自动包装为 action其内部的批量与不追踪语义就不是开发者显式可控的容易产生难以排查的行为差异。Formily 希望“函数是否是 action”由开发者显式决定。mobx-react-lite 的 observer 不支持 React 并发渲染。随着 React 18 并发渲染Concurrent Rendering成为主流渲染可能被中断、恢复、重放响应式订阅与派发如果与渲染阶段耦合不严会出现订阅泄漏或更新丢失。Formily 面向 React/React Native 的生态要求 observer 语义必须与并发渲染安全兼容。这四点共同指向一个结论通用响应式库在表单这一高频、高耦合的场景里存在结构性约束Formily 需要一把更贴合的“扳手”。1.1 重新造轮子的三个收益官方文档同时明确了自研而非打补丁的三个好处把控性更强可以为 Formily 场景做更深的优化定制。例如在 observable.ts 中observable函数仅一行调用createObservable(null, null, target)即完成深度代理创建同时把box/ref/deep/shallow/computed等注解静态挂载到函数上API 形态完全由自身掌控。不用考虑 MobX 的历史包袱内部实现依赖收集栈ReactionStack、批处理计数等可以按需精简代码更干净。版本风险隔离如果 MobX 出现 Breaking Change 或安全漏洞不会传导到 Formily 及其使用方。二、技术前提强依赖 ES Proxy不支持 IE重新造轮子的底气来自 ES Proxy。从源码看响应式对象通过 Proxy 劫持数据操作来实现“完美拦截”深度 observable 由createObservable递归构建见 internals.ts由 observable.ts 调用对象数据树的构建逻辑集中在 tree.tsmodel与define都依赖buildDataTree来建立可观测结构见 model.ts。因此formily/reactive 不支持 IE 浏览器这是官方文档明确说明的适用前提目标运行环境必须原生支持 Proxy。如果项目需要兼容 IE 11 及以下不应直接使用本包。三、核心概念四类原语虽然介绍文档本身聚焦于“动机”但它的延伸章节docs/guide/concept.zh-CN.md把整个模型抽象为四个概念这里结合源码做纵深解读。3.1 Observable可订阅对象Observable 是响应式模型的地基创建一个可订阅对象在每次操作其属性数据的过程中自动通知订阅者。formily/reactive 通过以下 API 创建 observableAPI语义对应源码observable(target)创建深度 observable 对象深劫持observable.tsobservable.deep深劫持 observable 对象同上注解实现见 annotations/observable.tsobservable.shallow浅劫持 observable 对象annotations/shallow.tsobservable.computed创建缓存计算器annotations/computed.tsobservable.box创建带get/set方法的 observable 对象annotations/box.tsobservable.ref创建引用级 observable 对象不深劫持内部annotations/ref.tsdefine(target, annotations)按注解定义 observable 领域模型model.tsmodel(target)自动领域模型getter 包装为 computed函数包装为 action其余数据深劫持model.ts从源码看model的实现非常直白遍历对象键用Object.getOwnPropertyDescriptor判断是否存在 getter是则标记observable.computed、是否为函数是则标记action其余字段统一标记为observable深劫持最后交给define通过buildDataTree建立数据树model.ts。3.2 Reaction订阅者Reaction 是“订阅者”角色它接收一个 tracker 函数执行时对 observable 属性的读操作进行依赖收集并建立绑定当该属性在其他地方发生写操作时触发 tracker 重复执行。整个订阅→派发是一个封闭的循环状态机每次执行都会重新收集依赖。对应的三个 APIautorun(tracker)自动执行的响应器创建后立即执行一次返回dispose函数。源码中它维护_memos与_effects两套队列用于 memo/effect 的缓存autorun.ts。reaction(tracker, subscriber, options)支持脏检查dirty check的响应器只有当 tracker 返回值oldValue ! currentValue或自定义equals判定不等时才触发 subscriber还支持fireImmediately选项autorun.ts。Tracker手动依赖追踪器track方法由用户手动触发构造时可注入自定义scheduler其默认调度行为是“边界为 0 时自动 dispose”tracker.ts。重要提醒来自官方文档一旦不再订阅 reaction一定要手动调用其返回的 dispose 函数否则会造成内存泄漏。在 React 组件中使用时通常配合useEffect的清理函数或在组件卸载时调用。3.3 Computed可缓存计算结果的 Reaction一句话概括computed 是一个可以缓存计算结果的 Reaction。只要 computed 函数内部依赖的 observable 数据不变就一直读取缓存结果依赖变化才重新计算。因此官方文档对 computed 有一个硬性要求computed 函数必须是纯函数内部依赖只能是 observable 数据或外部常量。如果引用了非 observable 的外部变量该变量变化时 computed 不会重新计算——这是缓存语义决定的不是 bug。3.4 Batch合并更新减少派发次数因为基于 Proxy 劫持任何一个原子操作都会触发 Reaction 执行。例如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()autorun默认执行一次打印加上obs.aa赋值、obs.bb赋值各触发一次共打印 3 次原子操作越多触发次数越多。使用batch将更新合并后只执行 2 次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() })源码层面batch是createBoundaryAnnotation(batchStart, batchEnd)的产物并额外提供batch.scope作用域批量与batch.endpoint批量结束后回调注册见 batch.ts。3.5 action高阶批量包装除了显式batch还可以用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 次。源码中action由createBoundaryAnnotation同时开启batchStart untrackStart结束时执行untrackEnd batchEndaction.ts并支持action.scope。四、最佳实践四条工程守则官方文档docs/guide/best-practice.zh-CN.md给出了 4 条可落地的实践建议分别对应性能与内存两大主题尽量少用observable/observable.deep深度包装非不得已时优先observable.ref/observable.shallow性能更好。深劫持需要递归构建数据树并给每个层级挂代理浅代理与引用级代理的构建和读写开销都更小。领域模型中多用 computed 计算属性它可以智能缓存计算结果避免重复派生计算。批量操作不是必须的但尽量多用batch模式减少 Reaction 的执行次数降低无效计算。使用autorun/reaction时务必调用 dispose即调用函数返回的二阶函数否则会内存泄漏。这与 autorun.ts 中返回disposeBindingReactions disposeEffects清理函数的实现一一对应。五、源码地图如何继续深入formily/reactive的公开 API 集中在 src/index.ts统一导出了batch、action、untracked、observable、model、autorun、tracker、observe、externals与类型定义是阅读全部导出面的入口。建议按以下顺序深入依赖收集与派发核心reaction.tsReactionStack、batchStart/batchEnd、disposeBindingReactions等注解实现src/annotations6 个注解文件对应box/ref/deep/shallow/computed/observable领域模型model.ts数据树构建tree.ts测试佐证src/tests共 16 个测试文件覆盖 observable、autorun、batch 等行为可运行npm test或在该目录下查阅具体用例验证本文所述行为。整套内核被上层 packages/react、packages/vue、packages/element、packages/next、packages/antd 等 UI 库复用并进一步支撑 packages/core 的表单模型与 packages/json-schema 的 Schema 驱动能力——理解本包就等于拿到了阅读 Formily 全链路源码的钥匙。结语formily/reactive是一次“场景驱动的自研”因为表单联动需要在 action 内收集依赖、需要过滤特殊对象、需要显式控制函数语义、需要兼容 React 并发渲染所以 Formily 基于 Proxy 重写了整个响应式内核换来更强的可控性、更干净的代码与版本风险隔离。在使用层面只要守住“浅代理优先、多用 computed 与 batch、及时 dispose”三条原则就能在表单应用中稳定获得预期的高性能表现。赞分享前端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组件Formily 核心架构解析基于 formily/reactive 响应式领域模型的设计原理Formily 核心架构解析基于 formily/reactive 响应式领域模型的设计原理 导读 本文围绕 packages/core/docs/guid前端UI组件Formily 响应式渲染formily/reactive-react 的 observer 与 Observer 使用指南Formily 响应式渲染formily/reactive react 的 observer 与 Observer 使用指南 本文是 Formily 官方文前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表