- 前端
- 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响应式内核对外暴露的一个"逃生舱"式 API,用于从 observable 代理对象中取回其背后的原始源数据。本文以 raw.zh-CN.md 文档为核心,先给出它的签名、用例与使用注意点,再深入到 externals.ts 的实现,讲清它依赖的ProxyRaw弱引用表与ObModelSymbol两条取数路径,并对照markRaw、toJS、isObservable等邻近 API 说明它"不推荐使用"的真正原因。读完本文,你将理解 reactive 代理层与源数据层的映射关系,并能在调试、序列化、与第三方库交互等场景中正确决策何时该用raw、何时该用toJS。
raw 是什么:从代理对象取回源数据
在 Formily 的响应式体系中,observable()返回的是一个 Proxy 包装后的代理对象(详见 observable 文档)。日常开发中读写代理对象即可触发依赖收集与响应,但在少数"必须拿到原始 JS 对象"的场景(例如与不感知 Proxy 的第三方库对接、序列化、对象身份比对)中,需要把代理"打回原形"。
raw正是为此设计的最小工具函数,其签名如下(见 raw.zh-CN.md):
interface raw<T extends object> { (target: T): T }入参是被 observable 化的对象,返回值是与之对应的源数据对象,泛型T保证传入与返回类型一致。
官方用例与关键注意点
文档给出了最小可运行示例(raw.zh-CN.md):
import { raw, observable } from '@formily/reactive' const obs = observable({}) obs.aa = { bb: 123 } console.log(raw(obs)) console.log(raw(obs.aa))对raw(obs)而言,返回的是obs背后未被代理的原始空对象;而对raw(obs.aa)而言,返回结果则取决于obs.aa这个对象是否被 observable 化——文档特别用<Alert>强调了这一点:
注意:只能获取当前对象的源数据,不包括深层对象属性
也就是说,raw只做"一层"逆向映射。obs.aa = { bb: 123 }的赋值发生在代理对象上,赋值完成后obs.aa是原始对象还是新的代理对象,由 observable 创建时对嵌套对象的递归代理策略决定;raw(obs)返回的原始对象上的aa属性依然是原始值,并不会因为调用了raw(obs)就顺带把obs.aa也还原成源数据。
源码实现:两条取数路径
raw的实际实现非常精炼,位于 externals.ts:
export const raw = <T>(target: T): T => { if (target?.[ObModelSymbol]) return target[ObModelSymbol] return ProxyRaw.get(target as any) || target }两条分支分别对应两类 observable 形态:
1. 基于 Proxy 的标准 observable(走ProxyRaw分支)
当调用observable()创建普通对象、数组或 Map/Set 等集合时,内部通过 internals.ts 的createNormalProxy/createCollectionProxy生成代理,并同时维护两套双向映射:
const proxy = new Proxy(target, baseHandlers) ProxyRaw.set(proxy, target) // proxy -> 源数据 if (shallow) { RawShallowProxy.set(target, proxy) } else { RawProxy.set(target, proxy) // 源数据 -> proxy }其中ProxyRaw与RawProxy都是定义在 environment.ts 中的WeakMap:
export const ProxyRaw = new WeakMap() export const RawProxy = new WeakMap() export const RawShallowProxy = new WeakMap()使用WeakMap而非普通 Map 是刻意的设计:键为代理对象、值为源对象,两者互不持有强引用,从而保证代理对象与源对象都能被 GC 正常回收,不会因取数映射造成内存泄漏。raw的ProxyRaw.get(target) || target语义也很清晰——命中映射则返回源数据,未命中(传入的本来就是普通对象)则原样返回。
2. 基于注解创建的模型(走ObModelSymbol分支)
model()、ref()、computed()等注解生成的对象不走 Proxy 包装,而是直接在生产对象上挂一个ObModelSymbol标记,指向真正的数据容器。例如 model.ts:
target[ObModelSymbol] = target以及 annotations/ref.ts 与 annotations/computed.ts 中的proxy[ObModelSymbol] = store。raw首行target?.[ObModelSymbol]的判空访问正是为了兼容这类对象;而annotations/box.ts(box()注解)则仍走 Proxy 路线,在 box.ts 中执行ProxyRaw.set(proxy, store)。
顺带一提,isObservable的实现与raw共享了同一套判定基础设施(externals.ts):
export const isObservable = (target: any) => { return ProxyRaw.has(target) || !!target?.[ObModelSymbol] }即"能被raw还原的对象"与"被判定为 observable 的对象"在判定逻辑上是一一对应的。
测试用例对语义的印证
raw的行为在集合类型的测试中得到了直接验证。collections-map.spec.ts 开头即断言:
test('should be a proper JS Map', () => { const map = observable(new Map()) expect(map).toBeInstanceOf(Map) expect(raw(map)).toBeInstanceOf(Map) })同样的断言也出现在 collections-set.spec.ts、collections-weakmap.spec.ts、collections-weakset.spec.ts 中。这印证了两个事实:
- 代理后的 Map 依然是
Map实例(拦截了get/has等内部方法做依赖追踪); raw(map)拿到的也是真正的Map实例(源对象),因此可以在其上直接执行raw(map).set('key', 'value')这样的原生操作——但要注意,绕过代理直接操作源对象将不会触发依赖收集与响应派发。
集合测试中还有一类高频用法值得注意:在autorun追踪函数内部通过raw(weakMap).has(value)(见 collections-weakmap.spec.ts)、raw(map).get('key')(见 collections-map.spec.ts)读取值。这是在"读取源数据的同时保持响应式"——因为raw(map)本身是稳定引用(每次返回同一个源对象),读取的是源对象的原始值,而依赖收集照常发生在代理层。这是一种合法的"脱壳"惯用法,与文档"不推荐使用"的警示并不冲突:警示针对的是"拿 raw 结果去直接操作/绕过响应式",而非"在追踪函数里读原始值"。
为什么文档明确"不推荐使用"
文档开篇即写明"通常情况下并不推荐使用该 API"(raw.zh-CN.md),结合源码可以从四个层面理解这一警示:
- 破坏响应式链:
raw返回的源对象没有 Proxy 拦截,直接对它读写不会触发依赖收集与副作用派发,等于绕过整个响应式系统; - 仅限单层还原:文档强调"不包括深层对象属性",深层次对象各自持有独立代理,需要逐层
raw才能全部还原,容易遗漏导致拿到"半代理半原始"的混合结构; - 身份不一致风险:同一源对象可能对应普通代理与浅代理(
RawShallowProxy)两种代理形态,混用raw与直接引用代理时容易产生"同一数据、多个身份"的困惑; - 有更合适的替代品:若目的是得到完整可序列化的纯 JS 数据,应使用
toJS()(实现见 externals.ts),它基于WeakSet防循环递归地把数组与普通对象逐层展开为普通值;若目的是让某个对象完全脱离响应式,应在源头使用markRaw()(externals.ts,内部以RAW_TYPE符号标记,使isSupportObservable直接放行不再代理),而不是事后用raw取回。
适用场景与总结
综合来看,raw是一个"低层工具箱"里的函数,最适合以下受限场景:
- 调试与断言:确认某个对象是否被代理、验证代理与源对象的身份关系;
- 与不感知 Proxy 的第三方库对接:需要传递"真 JS 对象"时临时脱壳;
- 响应式追踪内读取原始值:如集合测试所示,在
autorun/tracker回调里用raw(col).has(key)读值,既拿原始数据又不中断依赖收集。
在绝大多数业务代码中,优先考虑observable、markRaw与toJS的组合方案;只有在你明确知道自己要的是"当前对象这一层的源数据引用"时,才引入raw。理解它的两条取数路径(ProxyRaw弱引用表与ObModelSymbol标记)、单层还原语义以及WeakMap带来的无泄漏保证,就能在 Formily 的响应式世界里准确驾驭这个逃生舱。
- 前端
- UI组件
【免费下载链接】formily
📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3
相关推荐
Formily Reactive 的 raw API 详解:如何从 observable 对象中取回源数据
Formily Reactive 的 raw API 详解:如何从 observable 对象中取回源数据 导读 raw 是 @formily/reactive
前端UI组件为什么 Docker 版 Odysseus 备份不包含 ChromaDB 数据以及如何单独备份
为什么 Docker 版 Odysseus 备份不包含 ChromaDB 数据以及如何单独备份 如果你用 Docker Compose 运行 Odysseus,
人工智能AI 应用后端前端RAGAI AgentMCP 服务本地部署深度研究Rust 编译错误 E0533 详解:为什么模式匹配里不能用方法名,以及如何修复(附 rustc 源码分析)
Rust 编译错误 E0533 详解:为什么模式匹配里不能用方法名,以及如何修复(附 rustc 源码分析) E0533 是 rustc 在模式匹配阶段抛出的错
编程语言编译器语言运行时标准库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考