ARTICLE DETAIL

资讯详情

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

Formily Reactive toJS 详解:从 observable 到普通 JS 对象的深度递归转换

Formily Reactive toJS 详解:从 observable 到普通 JS 对象的深度递归转换 前端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点击查看免费下载导读toJS是 Formily 响应式核心 formily/reactive 提供的重要工具函数用于将 observable 响应式对象深度递归地转换回普通 JS 对象。在实际开发中当我们完成表单数据收集、需要将响应式状态提交给后端、序列化到本地存储或传递给非响应式库如 axios、echarts、富文本编辑器时toJS就是打通响应式世界与普通数据世界的标准出口。读完本文你将掌握toJS的完整签名、底层递归实现原理、与markRaw的联动行为以及循环引用场景下的安全用法。一、toJS 是什么toJS是 formily/reactive 对外导出的核心 API 之一其官方描述为深度递归将 observable 对象转换成普通 JS 对象。也就是说它会把通过observable()创建的响应式代理Proxy还原为无响应式能力的原生数据。转换后的结果与原始 observable不再共享依赖追踪关系后续对普通对象的修改不会触发任何 autorun 响应。一个需要特别注意的行为是如果对一个已经是 observable 的对象标记markRaw那么toJS不会将它转换成普通对象。这一行为在 toJS.zh-CN.md 与 markRaw.zh-CN.md 中均有明确说明其根源在源码实现中也有体现详见本文第三节。二、签名与基本用例toJS的类型签名非常简洁interface toJST { (target: T): T }即输入什么类型的值就返回什么类型的值泛型T保证类型在转换前后保持不变。注意它只接受一个参数深度转换是默认行为无需额外配置。官方文档给出的基础用例见 toJS.zh-CN.mdimport { observable, autorun, toJS } from formily/reactive const obs observable({ aa: { bb: { cc: 123, }, }, }) const js toJS(obs) autorun(() { console.log(js.aa.bb.cc) //变化时不会触发 }) js.aa.bb.cc 321在这个例子中obs是一个嵌套三层的 observable 对象obs.aa.bb.cc的读取会产生依赖收集toJS(obs)得到的是普通对象js其内部不再存在 Proxy 代理在autorun中读取js.aa.bb.cc不会建立任何响应式依赖因此注释说明变化时不会触发直接修改js.aa.bb.cc 321由于js是普通对象该赋值既不会触发任何反应也不会反向影响obs。这正是toJS的典型用途生成一份脱离响应式系统的数据快照。三、源码级原理toJS 的深度递归实现toJS的真实实现位于 packages/reactive/src/externals.ts完整逻辑如下export const toJS T(values: T): T { const visited new WeakSetany() const _toJS: typeof toJS (values: any) { if (visited.has(values)) { return values } if (values values[RAW_TYPE]) return values if (isArr(values)) { if (isObservable(values)) { visited.add(values) const res: any [] values.forEach((item: any) { res.push(_toJS(item)) }) visited.delete(values) return res } } else if (isPlainObj(values)) { if (isObservable(values)) { visited.add(values) const res: any {} for (const key in values) { if (hasOwnProperty.call(values, key)) { res[key] _toJS(values[key]) } } visited.delete(values) return res } } return values } return _toJS(values) }结合源码可以梳理出以下几个关键机制1. 深度递归 循环引用保护_toJS对数组isArr和普通对象isPlainObj分别递归处理每个元素/自有属性。为了应对对象自引用如obj.obj obj导致的无限递归实现使用WeakSet记录访问过的对象递归前visited.add(values)递归完成后visited.delete(values)。WeakSet不会阻止对象被垃圾回收且只在递归路径上生效适合作为访问栈标记。这一点在测试 packages/reactive/src/tests/externals.spec.ts 中有直接验证test(recursive references tojs, () { const obj: any { aa: 111 } obj.obj obj const obs observableany(obj) obs.obs obs expect(toJS(obs)).toBeTruthy() const arrObs observable([{ aa: 1 }, { bb: 2 }, { cc: 3 }]) expect(toJS(arrObs)).toEqual([{ aa: 1 }, { bb: 2 }, { cc: 3 }]) })2. 递归出口非 observable 原样返回只有被isObservable判定的对象才进入递归分支其余值原始类型、未被代理的函数、类实例等一律原样返回。isObservable的定义位于同一文件export const isObservable (target: any) { return ProxyRaw.has(target) || !!target?.[ObModelSymbol] }即对象要么是ProxyRawProxy → raw 映射见 environment.ts中登记过的 Proxy要么是带ObModelSymbol的模型对象二者都算 observable。3. markRaw 的短路RAW_TYPE 标记toJS内部有一行非常关键的判断if (values values[RAW_TYPE]) return valuesRAW_TYPE是一个模块级私有 Symbolexternals.ts由markRaw写入。这就是文档警告对已 observable 对象标记 markRaw 后toJS 不会转换它的实现根源一旦对象带有RAW_TYPE标记toJS会直接把它原样返回不做任何深拷贝。四、与 markRaw 的联动理解 RAW_TYPE 的语义markRaw的官方语义是标记任意一个对象或者类原型为永远不可被 observable 劫持优先级比 markObservable 高见 markRaw.zh-CN.md。其实现同样在 externals.tsexport const markRaw T(target: T): T { if (!target) return if (isFn(target)) { target.prototype[RAW_TYPE] true } else { target[RAW_TYPE] true } return target }要点如下函数类级标记对构造函数调用markRaw(Class)会在Class.prototype上写入RAW_TYPE从而影响该类的所有实例实例级标记对实例调用markRaw(instance)只影响当前实例与 toJS 的关系无论对象是否已被 observable 化只要带有RAW_TYPE标记toJS都将其视为应当原样透传的数据不做转换。这与markRaw 优先级最高的语义一致——被标记为 raw 的数据在toJS中同样保持原样。配套测试 externals.spec.ts 从多个角度验证了标记组合的优先级markRaw({ aa: 111 })后再observable()结果不可观察isObservable为 falsemarkRaw(Class)后new Class()的实例均不可观察markRaw(markObservable({...}))与markObservable(markRaw({...}))最终都不可观察证明markRaw 优先级高于 markObservable。五、isSupportObservable谁会被排除在 observable 之外toJS只转换 observable 数据而一个对象能否成为 observable由isSupportObservable同文件 externals.ts决定。该函数对普通对象有一系列排除规则if (target[RAW_TYPE]) return false if (target[OBSERVABLE_TYPE]) return true if ($$typeof in target _owner in target) return false // React 元素 if (target[_isAMomentObject]) return false // moment 对象 if (target[_isJSONSchemaObject]) return false // JSON Schema 对象 if (isFn(target[toJS])) return false // 自带 toJS if (isFn(target[toJSON])) return false // 自带 toJSON return true这些规则同样有测试覆盖externals.spec.tsReact 元素带$$typeof与_owner、moment 对象、JSON Schema 对象会被自动排除避免被代理破坏内部状态自带toJS/toJSON方法的对象不会被代理从而保留自定义序列化能力如 moment 的toJSONMap / WeakMap / Set / WeakSet 始终支持 observablenull、undefined等无效值不支持。理解这一点有助于避免为什么toJS对这个对象没生效的困惑如果对象本身根本没有被 observable 化toJS自然原样返回它。六、实用场景与注意事项1. 典型使用场景提交数据将表单响应式状态toJS化后交给 axios/fetch 发送避免 Proxy 对象在序列化时出现意外行为持久化把状态快照写入 localStorage/IndexedDB 或传给 postMessage保证存储的是纯数据对接外部库echarts、canvas、富文本等库通常不需要响应式传入普通对象更安全高效调试在控制台打印toJS(state)可以看到清晰可读的普通对象结构。2. 注意事项快照是一次性的toJS返回的是全新拷贝数组/对象均为新引用与源 observable 完全解耦修改互不影响markRaw 对象不会被转换被markRaw标记过的对象在toJS中原样透传这是文档明确声明并受源码保障的行为非 observable 原样返回如果传入的本来就是普通对象、函数或原始类型toJS不做任何处理类型保持签名(target: T): T保证转换前后 TypeScript 类型不变可直接用于类型安全的数据出口不推荐反向滥用频繁toJS会创建大量副本若需要拿到底层源数据而非拷贝官方提供rawAPI见 raw.zh-CN.md但官方同样注明通常情况下并不推荐使用。七、小结能力说明功能深度递归将 observable 转为普通 JS 对象签名toJST(target: T): T递归支持数组、普通对象深度遍历WeakSet防循环引用markRaw 联动带RAW_TYPE标记的对象原样返回不转换递归出口非 observable 值原样返回源码位置packages/reactive/src/externals.ts测试验证packages/reactive/src/tests/externals.spec.tstoJS虽小却是响应式数据走出 formily/reactive 世界的必经之路。理解它的递归策略、RAW_TYPE短路逻辑以及与isSupportObservable的配合能帮助你在表单提交、状态持久化和第三方库对接等场景中写出更稳妥的代码。赞分享前端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 深度解析toJS 如何把 observable 对象还原为普通 JS 对象Formily 响应式核心 API 深度解析toJS 如何把 observable 对象还原为普通 JS 对象 toJS 是 formily/reactiv前端UI组件Formily Reactive 的 raw API 详解如何从 observable 对象中取回源数据Formily Reactive 的 raw API 详解如何从 observable 对象中取回源数据 导读 raw 是 formily/reactive前端UI组件formily/reactive observe API 全解析深度/浅度监听 Observable 对象的所有写操作formily/reactive observe API 全解析深度/浅度监听 Observable 对象的所有写操作 observe 是 formily前端UI组件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表