ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

@formily/reactive observe API 详解:操作级监听、深/浅监听与变更事件模型

@formily/reactive observe API 详解:操作级监听、深/浅监听与变更事件模型 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),仅供参考
返回列表