Formily Reactive 的 raw API 详解:如何从 observable 对象中取回源数据
2026/9/23 13:43:28 网站建设 项目流程
  • 前端
  • 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
点击查看免费下载

导读

raw@formily/reactive响应式核心中一个用于「取回源数据」的底层工具函数。在 Formily 的响应式体系中,observable创建的对象是一个 Proxy 代理,日常读写都应经由代理完成以保证依赖收集与响应式更新;raw则提供了一条绕过代理、直接拿到原始对象的通道。本文基于 raw API 文档(中文版见 raw.zh-CN.md),结合 @formily/reactive 包的源码实现与测试用例,详细讲解该 API 的签名、行为边界、内部实现原理,以及与toJSmarkRaw等关联 API 的异同,帮助你在确有需要时安全、正确地使用它。

基本描述:从 observable 中获取源数据

raw的作用非常单一:从 observable 对象中获取其源数据(原始对象)

在 Formily 的响应式架构中,observable()会把传入的普通对象包装为 Proxy(相关实现见 internals.ts 中的createNormalProxy/createCollectionProxy)。此后我们对这个对象的所有读写都会被拦截,从而触发依赖收集与副作用通知。而raw返回的正是这个 Proxy 背后未被包装的原始对象。

文档同时给出了一个重要提醒,这也是本 API 的第一条使用边界:

注意:只能获取当前对象的源数据,不包括深层对象属性。

raw(obs)只能拿到obs这一层对应的源对象;如果obs的某个属性值是嵌套的 observable 代理(如示例中的obs.aa),需要单独对obs.aa调用raw,才能拿到那一层嵌套对象的源数据。

函数签名

文档中给出的类型签名如下:

interface raw<T extends object> { (target: T): T }
  • 入参:任意对象target,通常是observable()创建的代理对象;
  • 返回值:类型与入参相同,实际返回对应的源对象;
  • 泛型约束为object,因为observable体系只针对对象(含数组、Map、Set 等集合类型)生效。

从类型签名可以看出,raw是类型安全的:传入代理得到的是其源对象类型,编译期即可保持一致。

基础用例

文档给出了最直接的使用示例:

import { raw, observable } from '@formily/reactive' const obs = observable({}) obs.aa = { bb: 123 } console.log(raw(obs)) // 输出源对象(原始对象) console.log(raw(obs.aa)) // 输出嵌套对象 aa 的源对象

这里有两层含义需要展开:

  1. obs.aa = { bb: 123 }这行赋值发生在代理上。由于observable默认是深响应式的,obs.aa本身也会被包装为 observable 代理(见 internals.ts 中createObservable对嵌套值的递归处理)。
  2. raw(obs)拿到的是一层源对象;raw(obs.aa)拿到的是嵌套对象aa这一层的源对象。两者互不包含——这正是文档中「不包括深层对象属性」的实际体现。

源码实现:raw 到底做了什么

raw的实际实现位于 externals.ts:

export const raw = <T>(target: T): T => { if (target?.[ObModelSymbol]) return target[ObModelSymbol] return ProxyRaw.get(target as any) || target }

该实现依赖 environment.ts 中维护的两个全局映射:

  • ProxyRaw: WeakMap:记录「代理对象 → 源对象」的映射;
  • ObModelSymbolSymbol('ObModelSymbol'),用于模型(modelAPI)场景下标记源对象。

因此raw的取值逻辑分为两条路径:

  1. 如果targetmodel()创建的模型对象(带有ObModelSymbol),则直接返回target[ObModelSymbol]记录的源对象;
  2. 否则从ProxyRaw这个 WeakMap 中查询代理对应的源对象;如果查询不到(说明传入的本身就不是 observable 代理),则原样返回入参

这意味着raw是一个「安全降级」的函数:对任意非代理对象调用raw,得到的就是它自己,不会报错。这一点非常实用,例如在不确定一个值是否被observable包装过时,可以直接调用raw而不必先做判断。

同时,由于ProxyRaw是 WeakMap(键为对象,弱引用),raw的查询不会造成内存泄漏,也无需手动清理映射关系。

代理创建的对应关系

要理解raw为什么有效,需要看代理创建时两个映射是如何成对写入的(internals.ts):

const createNormalProxy = (target: any, shallow?: boolean) => { const proxy = new Proxy(target, baseHandlers) ProxyRaw.set(proxy, target) // 代理 -> 源对象 if (shallow) { RawShallowProxy.set(target, proxy) } else { RawProxy.set(target, proxy) } return proxy }
  • ProxyRaw保存「代理 → 源对象」,供raw反查;
  • RawProxy/RawShallowProxy保存「源对象 → 代理」,供内部判断一个源对象是否已有对应代理(避免重复包装)。

两者互为反向索引,raw就是利用了其中的ProxyRaw方向。

行为边界与注意事项

1. 只取当前层,不取深层

再次强调文档中的核心警告:raw只返回当前对象的源数据,嵌套的深层对象属性不会被一并还原。若需要递归地把整个对象树还原为纯 JS 数据,应使用toJS(同为 externals.ts 中导出的 API),它会对可观察对象进行递归深拷贝式还原,并内置WeakSet防止循环引用导致的死循环。

2. 拿到的源对象不再具备响应式能力

通过raw拿到的源对象是原始引用,直接对它进行读写不会触发任何依赖收集或响应式更新。因此在业务代码中,用raw取到的数据只能用于「只读查看」或「一次性导出」等场景,不应作为持续读写的数据源。

3. 为什么不推荐使用

文档明确指出「通常情况下并不推荐使用该 API」。原因在于:响应式体系的正确用法是始终经由代理访问数据,这样autorunreactionobserver等才能正确收集依赖。一旦通过raw绕过代理访问,很容易破坏依赖收集的完整性,造成视图不更新或逻辑时序错乱的隐性 bug。绝大多数「需要原始数据」的场景,都可以改用toJS完成。

4. 集合类型同样适用

rawMapSetWeakMapWeakSet等集合类型的 observable 代理同样生效(createCollectionProxy同样会写入ProxyRaw,见 internals.ts)。在 collections-map.spec.ts 等测试中可以看到:

expect(raw(map)).toBeInstanceOf(Map) expect(raw(set)).toBeInstanceOf(Set) expect(raw(weakMap)).toBeInstanceOf(WeakMap) expect(raw(weakSet)).toBeInstanceOf(WeakSet)

5. 与 markRaw 的关系

raw(取回源数据)与markRaw(标记对象为不可响应)是两条互补的边界工具:

  • markRaw用于在observable包装时跳过某些对象(externals.ts),其实现是给目标对象打上RAW_TYPE标记;
  • isSupportObservable在判断「对象是否可被包装」时,会优先检查RAW_TYPE标记并直接返回false(externals.ts),从而保证被markRaw标记过的对象永远不会成为代理。

也就是说:markRaw阻止对象被代理,raw则是把已被代理的对象还原。两者都服务于「在某些场景下需要访问原始对象」的需求,但入口与语义不同。

测试用例佐证

raw的行为在 packages/reactive/src/tests目录下有充分覆盖,除上述集合类型断言外,还有大量用例通过raw直接操作源对象来验证响应式行为:

  • 在 collections-set.spec.ts 中,raw(set).add('value')raw(set).delete('value')raw(set).clear()直接作用于源 Set;
  • 在 collections-map.spec.ts 中,raw(map).set('key', 'Hello')raw(map).delete('key')raw(map).clear()同理;
  • 在 collections-weakmap.spec.ts 与 collections-weakset.spec.ts 中,也通过raw对 Weak 集合执行增删操作。

这些用例一方面验证了raw返回的确实是可直接操作的原生集合实例,另一方面也演示了raw的典型测试用法——在测试中绕过代理直接操控源对象,以便精确构造或断言底层状态。

总结与建议

要点说明
作用从 observable 代理对象中取回源数据
签名raw<T extends object>(target: T): T
返回规则代理 → 源对象;模型对象 →ObModelSymbol记录值;非代理 → 原样返回
深层限制只取当前层源数据,不递归还原深层属性
推荐替代需要递归还原纯数据时使用toJS
使用禁忌拿到的源对象不具备响应式能力,不应作为常规读写数据源

raw@formily/reactive中一个「存在但默认不应使用」的低层 API。理解它的实现(ProxyRaw反查)与边界(仅当前层、非响应式),有助于你深入理解 Formily 响应式体系中「代理 / 源对象」的双层结构;而在实际业务中,请优先遵循官方建议,用toJS或直接经由代理访问数据,仅在测试、调试或确需绕过代理的极少数场景下才使用raw

  • 前端
  • 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
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询