- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
导读
raw是@formily/reactive响应式核心中一个用于「取回源数据」的底层工具函数。在 Formily 的响应式体系中,observable创建的对象是一个 Proxy 代理,日常读写都应经由代理完成以保证依赖收集与响应式更新;raw则提供了一条绕过代理、直接拿到原始对象的通道。本文基于 raw API 文档(中文版见 raw.zh-CN.md),结合 @formily/reactive 包的源码实现与测试用例,详细讲解该 API 的签名、行为边界、内部实现原理,以及与toJS、markRaw等关联 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 的源对象这里有两层含义需要展开:
obs.aa = { bb: 123 }这行赋值发生在代理上。由于observable默认是深响应式的,obs.aa本身也会被包装为 observable 代理(见 internals.ts 中createObservable对嵌套值的递归处理)。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:记录「代理对象 → 源对象」的映射;ObModelSymbol:Symbol('ObModelSymbol'),用于模型(modelAPI)场景下标记源对象。
因此raw的取值逻辑分为两条路径:
- 如果
target是model()创建的模型对象(带有ObModelSymbol),则直接返回target[ObModelSymbol]记录的源对象; - 否则从
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」。原因在于:响应式体系的正确用法是始终经由代理访问数据,这样autorun、reaction、observer等才能正确收集依赖。一旦通过raw绕过代理访问,很容易破坏依赖收集的完整性,造成视图不更新或逻辑时序错乱的隐性 bug。绝大多数「需要原始数据」的场景,都可以改用toJS完成。
4. 集合类型同样适用
raw对Map、Set、WeakMap、WeakSet等集合类型的 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
相关推荐
@formily/reactive hasCollected 详解:如何检测依赖是否被收集
@formily/reactive hasCollected 详解:如何检测依赖是否被收集 hasCollected 是 @formily/reactive 响
前端UI组件Clarity热力图生成指南:深度解析用户点击和滚动行为模式
Clarity热力图生成指南:深度解析用户点击和滚动行为模式 Clarity是一款强大的行为分析库,它通过监控DOM变化和用户交互来生成聚合洞察,帮助开发者深入
人工智能语音音频AI 应用OpenBMBFormily Reactive 的 markObservable:强制接管对象的响应式劫持机制
Formily Reactive 的 markObservable:强制接管对象的响应式劫持机制 导读 markObservable 是 @formily/re
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考