news 2026/9/24 10:04:12

Formily Reactive 源码解析:raw API 如何取回 Observable 源数据及为什么官方不推荐使用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Formily Reactive 源码解析:raw API 如何取回 Observable 源数据及为什么官方不推荐使用
  • 前端
  • 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响应式内核对外暴露的一个"逃生舱"式 API,用于从 observable 代理对象中取回其背后的原始源数据。本文以 raw.zh-CN.md 文档为核心,先给出它的签名、用例与使用注意点,再深入到 externals.ts 的实现,讲清它依赖的ProxyRaw弱引用表与ObModelSymbol两条取数路径,并对照markRawtoJSisObservable等邻近 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 }

其中ProxyRawRawProxy都是定义在 environment.ts 中的WeakMap

export const ProxyRaw = new WeakMap() export const RawProxy = new WeakMap() export const RawShallowProxy = new WeakMap()

使用WeakMap而非普通 Map 是刻意的设计:键为代理对象、值为源对象,两者互不持有强引用,从而保证代理对象与源对象都能被 GC 正常回收,不会因取数映射造成内存泄漏。rawProxyRaw.get(target) || target语义也很清晰——命中映射则返回源数据,未命中(传入的本来就是普通对象)则原样返回。

2. 基于注解创建的模型(走ObModelSymbol分支)

model()ref()computed()等注解生成的对象不走 Proxy 包装,而是直接在生产对象上挂一个ObModelSymbol标记,指向真正的数据容器。例如 model.ts:

target[ObModelSymbol] = target

以及 annotations/ref.ts 与 annotations/computed.ts 中的proxy[ObModelSymbol] = storeraw首行target?.[ObModelSymbol]的判空访问正是为了兼容这类对象;而annotations/box.tsbox()注解)则仍走 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 中。这印证了两个事实:

  1. 代理后的 Map 依然是Map实例(拦截了get/has等内部方法做依赖追踪);
  2. 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),结合源码可以从四个层面理解这一警示:

  1. 破坏响应式链raw返回的源对象没有 Proxy 拦截,直接对它读写不会触发依赖收集与副作用派发,等于绕过整个响应式系统;
  2. 仅限单层还原:文档强调"不包括深层对象属性",深层次对象各自持有独立代理,需要逐层raw才能全部还原,容易遗漏导致拿到"半代理半原始"的混合结构;
  3. 身份不一致风险:同一源对象可能对应普通代理与浅代理(RawShallowProxy)两种代理形态,混用raw与直接引用代理时容易产生"同一数据、多个身份"的困惑;
  4. 有更合适的替代品:若目的是得到完整可序列化的纯 JS 数据,应使用toJS()(实现见 externals.ts),它基于WeakSet防循环递归地把数组与普通对象逐层展开为普通值;若目的是让某个对象完全脱离响应式,应在源头使用markRaw()(externals.ts,内部以RAW_TYPE符号标记,使isSupportObservable直接放行不再代理),而不是事后用raw取回。

适用场景与总结

综合来看,raw是一个"低层工具箱"里的函数,最适合以下受限场景:

  • 调试与断言:确认某个对象是否被代理、验证代理与源对象的身份关系;
  • 与不感知 Proxy 的第三方库对接:需要传递"真 JS 对象"时临时脱壳;
  • 响应式追踪内读取原始值:如集合测试所示,在autorun/tracker回调里用raw(col).has(key)读值,既拿原始数据又不中断依赖收集。

在绝大多数业务代码中,优先考虑observablemarkRawtoJS的组合方案;只有在你明确知道自己要的是"当前对象这一层的源数据引用"时,才引入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

项目地址:https://gitcode.com/gh_mirrors/fo/formily
点击查看免费下载

相关推荐

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 9:54:52

一颗电阻实现芯片级电机限流保护

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 9:52:32

深度解读Work Agent长程任务的中间校验与自主优化机制

AI技术的落地路径&#xff0c;过去几年已经走过了清晰的演化脉络&#xff1a;最早的单轮问答模式下&#xff0c;用户输入一个问题&#xff0c;系统返回对应答案&#xff0c;交互链路在单次请求内就完成闭环&#xff1b;随后多轮对话能力成熟&#xff0c;系统可以记住前序几轮的…

作者头像 李华
网站建设 2026/9/24 9:50:57

最近体验了这款免费云服务器,简单分享下使用感受。

服务器地址我就不说了&#xff0c;当前状态正常运行&#xff0c;支持 VNC 连接&#xff0c;可以自主重启、开关机&#xff0c;也支持重装系统、快照备份&#xff0c;基础功能很齐全。免费机型采用发帖延期机制&#xff0c;每次审核通过可以延长 5 天使用时间。需要在指定平台发…

作者头像 李华
网站建设 2026/9/24 9:45:11

国产操作系统下语音推理服务的依赖排查

一、问题背景语音模型在开发环境能够运行&#xff0c;并不代表复制到目标设备后也能正常启动。在国产操作系统环境中&#xff0c;问题可能来自动态库、处理器架构、推理框架、驱动或音频设备依赖。排查时应避免一上来就重装系统或更换模型&#xff0c;先收集环境信息&#xff0…

作者头像 李华
网站建设 2026/9/24 9:39:23

CADe SIMU电气控制仿真入门:原理图设计与动态验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华