vue 3 的 reactive 采用懒递归代理实现深度监听,仅在首次访问嵌套属性时逐层代理可响应式对象,跳过原始类型和不可代理内置对象。

Vue 3 的 reactive 实现嵌套对象的深度监听,靠的不是“一上来就全递归”,而是按需代理 + 懒递归——访问哪一层,才把那一层转成响应式,既精准又节省开销。
懒递归:用到才代理,不碰就不动
当你写 reactive({ a: { b: { c: 1 } } }),Vue 只立刻给最外层对象加 Proxy;a 还是普通对象,b 和 c 根本没被处理。
- 第一次读
state.a→ 触发get拦截 → 发现值是对象 → 立即调用reactive(a),生成新 Proxy 并缓存 - 接着读
state.a.b→ 同样逻辑,再对b执行一次reactive - 如果
state.x.y从未被访问,对应对象始终不会被代理,也不消耗内存和初始化时间
哪些类型会被继续递归?哪些会停住?
递归只发生在“可代理”的结构上,其他类型直接终止,避免无效操作:
- ✅ 继续递归:普通对象(
{})、数组([])、Map、Set、WeakMap、WeakSet - ❌ 不递归:原始类型(
string、number、boolean、null、undefined)、内置不可代理对象(Date、RegExp、Promise)
也就是说,只要中间出现一个合法对象,响应式链就会自然延伸下去,无需手动加 deep: true。
ref 的深度能力也来自 reactive
ref({ x: { y: 1 } }) 表面看是包装一层,实际等价于:
reactive({ value: reactive({ x: reactive({ y: 1 }) }) })
所以 myRef.value.x.y 的变化能被精确追踪——它的深度响应性,本质是 reactive 的懒递归机制在底层支撑的。
watch 里要注意:默认不自动深度触发
虽然 reactive 对象本身支持深度响应,但 watch(userInfo, ...) 默认只监听顶层属性的替换或新增,userInfo.profile.name = '李四' 不会触发回调。
- 想监听嵌套属性变化,推荐写 getter 函数:
watch(() => userInfo.profile.name, ...) - 或者开启
deep: true选项(注意:它会强制遍历整个对象树,可能带来性能负担) - 避免直接
watch(userInfo, ...)期望捕获任意嵌套变更,这是常见误区
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










