formily/reactive observe API 详解操作级监听、深/浅监听与变更事件模型【免费下载链接】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导读observe是formily/reactive中用于监听 observable 对象所有写操作的底层 API与autorun/reaction/Tracker的依赖收集机制截然不同。本文以 observe.md 为骨架结合 observe.ts、tree.ts 等源码与 observe.spec.ts 测试用例系统讲解它的类型签名、深/浅监听行为、IChange变更对象结构、DataNode树匹配原理以及dispose释放机制读完即可在实际项目中熟练使用。一、observe 与 autorun/reaction/Tracker 的本质区别在formily/reactive中autorun、reaction、Tracker都属于依赖收集型API它们在执行回调时跟踪读取了哪些属性之后仅当这些被读取过的属性发生变更时才重新执行。observe则完全不同——它不关心你读了什么只关心你写了什么。一旦通过observe(target, observer)注册监听目标对象上的所有写操作新增属性、修改属性、删除属性、数组增删等都会触发回调无论回调内部是否读取过对应字段。这一点在 observe.md 中有明确说明Very different from autorun/reaction/Tracker, using observe will monitor all operations of observable objects, support deep monitoring and shallow monitoring文档同时给出一个关键约束以Alert强调Note: The read operation will not be monitored即get操作不会被监听到observe 只面向写操作。二、类型签名逐项解析文档给出的完整签名如下type PropertyKey string | number | symbol type ObservablePath Arraystring | number type OperationType | add | delete | clear | set | get | iterate | has interface IChange { key?: PropertyKey path?: ObservablePath object?: object value?: any oldValue?: any type?: OperationType } interface IDispose { (): void } interface observe { ( target: object, observer?: (change: IChange) void, deep?: boolean //default is true ): IDispose //Release the monitor }各参数与返回值的含义成员说明target要监听的 observable 对象普通对象、数组或observable()创建的对象observer可选回调每次写操作发生时收到一个IChange描述对象deep可选布尔值默认true表示深度监听传false退化为浅监听返回值IDispose释放函数调用后监听立即解除OperationType覆盖了代理层会拦截的全部操作类型其中set属性赋值、add新增属性/数组 push、delete删除属性、clear集合清空、iterate遍历与has均属于写或查询语义而get虽然出现在类型联合中但正如文档所述读取操作不会被监听。IChange的path字段类型为ObservablePathArraystring | number它对应源码中DataChange的pathgetter——由this.node.path.concat(this.key)计算而来即从被监听根节点到变更节点的完整路径见 tree.ts数组中每个元素要么是属性名字符串要么是数组下标数字。三、源码实现原理ObserverListeners 与 DataNode 树observe的实现位于 observe.ts核心逻辑只有一层闭包export const observe ( target: object, observer?: (change: DataChange) void, deep true ) { const addListener (target: any) { const raw getRaw(target) const node getDataNode(raw) const listener (operation: IOperation) { ... } if (node isFn(observer)) { ObserverListeners.add(listener) } return () { ObserverListeners.delete(listener) } } if (target typeof target ! object) throw Error(Can not observe ${typeof target} type.) return addListener(target) }实现要点类型校验target若存在且不是object类型例如函数直接抛出Can not observe ${typeof target} type.错误。测试用例invalid target中expect(() observe(function () {})).toThrowError()验证了这一点见 observe.spec.ts。Raw 归一化通过getRaw(target)取得原始对象observable返回的是 Proxyraw()从ProxyRaw弱映射中还原出原始对象见 externals.ts再经getDataNode(raw)拿到对应的DataNode。全局监听器注册监听器并不挂在对象上而是统一加入ObserverListenersArraySetObservableListener见 environment.ts。代理层每次写操作都会广播IOperation给所有注册的 listener由 listener 内部做节点匹配过滤。dispose 的本质返回的闭包执行ObserverListeners.delete(listener)即从全局集合中移除该监听器此后写操作不再触发回调。深/浅监听的节点匹配listener 内部的匹配逻辑observe.ts是理解deep参数的关键const listener (operation: IOperation) { const targetRaw getRaw(operation.target) const targetNode getDataNode(targetRaw) if (deep) { if (node.contains(targetNode)) { observer(new DataChange(operation, targetNode)) return } } if ( node targetNode || (node.targetRaw targetRaw node.key operation.key) ) { observer(new DataChange(operation, targetNode)) } }deep true默认只要发生操作的节点targetNode位于被监听节点node的子树内node.contains(targetNode)就触发回调。DataNode.contains沿着父链向上查找isEqual匹配的祖先节点见 tree.ts因此对根对象observe(obs, handler)后obs.aa.bb.cc.push(44)这类深层修改也会被捕获。deep false浅监听仅当操作节点恰好等于被监听节点node targetNode或操作就是对该节点自身属性 key 的直接赋值node.targetRaw targetRaw node.key operation.key时才触发。测试用例shallow observe中obs.dd 123触发 1 次而obs.aa.bb.cc.push(44)不触发见 observe.spec.ts。DataNode之间的父子关系由buildDataTree在createObservable递归构建对象树时建立见 internals.ts 与 tree.ts并分别存放在RawNode弱映射或ObModelNodeSymbol上。四、基础用法示例文档给出的最小可用示例import { observable, observe } from formily/reactive const obs observable({ aa: 11, }) const dispose observe(obs, (change) { console.log(change) }) obs.aa 22 dispose()执行流程observable({ aa: 11 })创建响应式代理入口见 observable.tsobserve(obs, handler)注册监听并返回disposeobs.aa 22触发一次set操作回调收到形如{ key: aa, type: set, value: 22, oldValue: 11, path: [aa], object: obs }的变更对象dispose()解除监听后续写入不再回调。observe与observable均从 index.ts 统一导出export * from ./observe见 index.ts可直接import { observable, observe } from formily/reactive使用。五、深入行为测试用例揭示的边界语义observe.spec.ts 中包含了若干值得注意的行为细节1. 深度监听触发次数observe(obs, handler) obs.dd 123 // 触发 1 次 obs.aa.bb.cc.push(44) // 触发 1 次共 2 次 delete obs.aa // 触发 1 次共 3 次对根对象做深监听时任意层级的属性赋值、数组 push、属性删除都会各自触发一次回调。2. 数组元素替换与根替换root replaceobserve(obs, handler1) observe(obs.aa, handler) obs.aa { mm: 123 } // handler1 触发 1 次handler 也触发 1 次当obs.aa被子对象整体替换时对obs的监听和对obs.aa的监听都会收到通知——这正是深监听模式下contains匹配与浅监听“节点自身/直接属性”匹配共同作用的结果见 observe.spec.ts。3. 动态挂载子树dynamic treeconst tree observableany({}) const childTree observable({}) tree.children childTree observe(tree, handler) tree.children.aa 123 // 触发 1 次即使子对象是在监听之后动态挂载到树上的深监听依然能捕获其内部变更见 observe.spec.ts。这与DataNode.contains的父链查找实现相呼应——子节点通过parentgetter 回溯到挂载点只要祖先链上命中被监听节点即可触发。4. 数组删除后下标复用observe(array, (change) { if (change.type set change.key value) { fn(change.path?.join(.)) } }) array.splice(0, 1) // 删除首项后原第 1 项顶到下标 0 array[0].value 3 // path 仍为 0.value数组splice删除元素后下标会复用由于path是基于当前树结构实时计算的DataNode.path由父链拼接 key删除后新顶替项修改时其path依旧正确见 observe.spec.ts。IChange.path也是回调中判断变更位置的推荐手段。5. dispose 幂等且立即生效dispose() obs.aa 123 // 不再触发回调次数保持不变调用dispose()后监听立即失效见 observe.spec.ts适合在组件卸载、副作用清理等场景中释放资源。六、适用场景与注意事项适用场景调试与审计观察某个 observable 对象的全部写操作轨迹而不必预先知道会改动哪些字段跨层级联动监听一棵表单/状态树的任意层级变更做持久化、同步或日志与依赖收集型 API 互补autorun关注“我读过的字段”observe关注“谁动了这个对象”两者可以组合使用。注意事项读取不触发obs.aa这类读取操作不会进入回调需要响应式读取请使用autorun/Tracker或 React/Vue 的 observer 绑定必须 observableobserve面向formily/reactive的 observable 对象普通 JS 对象没有对应的DataNode无法被监听deep默认开启若只想监听对象自身的直接写操作含整体替换子属性显式传入false可减少不必要的回调类型约束target不能是非对象类型如函数否则会抛错及时释放监听器注册在全局ObserverListeners集合中长生命周期对象若不调用返回的dispose回调将持续存在注意避免内存泄漏。七、小结observe是formily/reactive中面向“写操作”的监听 API以(target, observer?, deep?)三参数注册、返回dispose释放函数默认深度监听整棵DataNode树浅监听只关注节点自身及其直接属性赋值回调中的IChange携带key / path / object / value / oldValue / type完整变更信息。其实现依托 observe.ts 的全局ObserverListeners注册机制与 tree.ts 的DataNode.contains树匹配相关边界行为均有 observe.spec.ts 测试用例佐证。需要深入源码时可继续阅读 types.tsIOperation/IChange定义、environment.tsObserverListeners/RawNode以及 externals.tsraw归一化。【免费下载链接】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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
等保2.0技术落地:从条款到代码的37项刚性实现 简介:本资源是一份面向企业安全负责人、等保测评人员及网络安全工程师的《等保2.0基本要求》深度解读精华文档,聚焦网络安全部分从1.0到2.0的关键演进与落地应对。全文系统梳理结构安全、访问控制、安全审计、边界完整性、入侵防范、恶意代码防范等核心条… · 2026/9/23 22:26:57
基于Python的校园一卡通消费行为分析:从清洗到分层结果集 简介:这是一份基于Python的学生校园消费行为分析完整项目,主要面向计算机专业期末大作业、课程设计及需要项目实战练习的学习者,难度适中。项目以DFM模型为核心,围绕高校校园消费数据展开处理、特征分析与结果展示,源码… · 2026/9/23 22:26:44
Python京东价格监控系统实战:爬虫、SQLite与定时提醒 简介:基于Python的京东价格监控系统完整源码,面向有商品比价需求的Python学习者和开发者,解决手动查看价格信息滞后、无法及时决策的痛点。系统整合Requests与Selenium两种爬取方式,支持通过JS接口或页面渲染获取价格,… · 2026/9/23 23:01:28
MATLAB尖峰检测实战:从findpeaks到物理约束驱动的工业级算法 简介:本资源是一套面向信号处理初学者与神经科学方向研究者的MATLAB尖峰自动检测算法实现,聚焦EEG脑电图中的棘波与海尖峰识别任务,解决噪声背景下微弱异常事件的精准定位难题。压缩包仅含1个核心MATLAB脚本(.m文件)&a… · 2026/9/23 23:01:28
Atlas 300V 24G部署YOLO全攻略:从推理卡定位到模型转换 在项目现场待久了,经常被同事问到一个问题:“这块Atlas 300V 24G到底算不算运算加速卡?”刚接触昇腾平台的人,看到“加速卡”三个字容易下意识往GPU上想,看到“24G”又会误以为和显卡显存一样。其实这个问题的答案直接… · 2026/9/23 23:01:03
Faster-RCNN PCB缺陷检测实战:数据准备、训练与评估全解析 简介:基于Python和Faster-RCNN的PCB元器件缺陷检测项目,提供完整源码、开发文档与项目解析,面向毕业设计、课程设计与实际项目开发场景。项目代码已经过严格测试,可直接运行并在此基础上二次扩展。资源包共79个文件,其… · 2026/9/23 23:01:03
双色球杀号公式实战:缩水工具与回测方法论 1. 杀号公式到底在杀什么:先搞清楚它的数学边界很多人第一次接触“杀号公式”这四个字,脑子里浮现的画面是某种能精准排除废号的神秘算法。我刚开始研究这个方向时也这么想,后来把最近几十期的开奖数据拉出来做了几轮回测,才意识到… · 2026/9/23 23:01:03
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29