vue 3 的 reactive 是按需懒递归代理:仅在首次访问嵌套属性时才对对应对象执行 reactive,基础类型、内置对象及 shallowreactive/ref 显式控制处终止递归。

Vue 3 的 reactive 不是“一次性把所有嵌套对象全变成 Proxy”,而是**按需、懒执行、逐层递归代理**——你第一次读取某个嵌套属性时,它才被自动包装成响应式。
核心机制:get 拦截中触发递归 reactive
当你调用 reactive({ a: { b: { c: 1 } } }),Vue 只给顶层对象加一层 Proxy。真正递归发生在你访问属性的过程中:
- 读取
obj.a→ 触发get拦截 → 发现a是普通对象 → 立刻对a执行reactive(a),生成新 Proxy - 再读取
obj.a.b→ 再次触发get→ 对b执行reactive(b) -
c是基础类型(number),不再代理,只保留原始值
为什么叫“懒递归”?
没被访问过的路径,不会提前创建 Proxy。比如:
const state = reactive({ user: { profile: { name: 'A' }, settings: { theme: 'light' } } })- 模板里只用了
{{ state.user.profile.name }}→ 只有user和profile被代理,settings仍是普通对象 - 修改
state.user.settings.theme不会触发更新 —— 因为该路径从未被 track 过
哪些情况会中断递归?
不是所有嵌套值都会被继续代理,以下情形会自然终止:
- 基础类型:string / number / boolean / null / undefined / Symbol → 直接返回,不代理
- 内置对象:Date、RegExp、Map、Set、Promise 等 → Vue 跳过代理,仅作浅层响应(如 Map 实例本身可被替换,但内部键值不响应)
- 显式控制:使用
shallowReactive或shallowRef→ 顶层代理后立即停止,子对象保持普通状态
ref 里的对象也走这套逻辑
ref({ x: { y: 1 } }) 看似只包了一层,其实内部是:
ref → 包装 { value: reactive({ x: reactive({ y: 1 }) }) }
也就是说:ref 对对象的处理,底层仍依赖 reactive 的递归代理机制,只是多了一层 .value 访问入口。











