Vue.js 的 reactive 通过 Proxy 递归代理嵌套对象,get 时按属性粒度收集依赖,set 时精准触发更新;需避免直接替换对象、数组索引赋值或解构赋值导致响应式丢失。

Vue.js 的 reactive 对嵌套对象的响应式更新,核心在于 递归代理 + 依赖收集 + 触发通知,不是靠“监听属性变化”,而是靠访问时建立依赖、赋值时触发更新。
Proxy 拦截所有层级的读写操作
reactive 内部用 Proxy 包裹原始对象,对 get 和 set 进行拦截:
-
get 拦截:首次访问某个嵌套属性(如
obj.a.b.c)时,自动调用track()收集当前正在执行的副作用(如组件 render 函数)作为依赖;若该属性值仍是对象,还会递归调用reactive()将其也变成响应式——所以嵌套对象默认就是响应式的,无需手动处理。 -
set 拦截:修改任意层级属性(包括新增或删除)时,触发
trigger(),通知所有依赖该属性的副作用重新执行(比如视图重渲染)。
依赖是按“属性粒度”收集的,不是对象粒度
Vue 不会为整个对象建立一个大依赖,而是为每个被读取的属性单独收集依赖。例如:
const state = reactive({
user: {
profile: { name: 'Alice', age: 30 },
settings: { theme: 'dark' }
}
})
如果模板中只用了 {{ state.user.profile.name }},那只有 profile.name 这个路径被 track;修改 state.user.settings.theme 不会触发该组件更新。
立即学习“前端免费学习笔记(深入)”;
这种细粒度依赖让更新更精准,但要注意:访问未定义的嵌套路径(如 state.user.contact.phone)时,由于 contact 是 undefined,JS 会报错,不会进入 proxy 的 get —— 所以深层属性需确保路径存在,或用可选链 ?. + v-if 等做保护。
深层赋值和响应式丢失的常见陷阱
以下操作会破坏响应式:
-
直接替换嵌套对象:
state.user = { profile: {...} }→ 新对象没被 proxy,需用Object.assign(state.user, newProfile)或逐个赋值。 -
通过索引设置数组项:
arr[0] = newValue不触发更新(Vue 3 已修复大部分情况,但仍有边界),推荐用arr.splice(0, 1, newValue)或arr = [...arr]。 -
解构后赋值:
const { name } = state.user.profile; name = 'Bob'→ 解构出的是普通值,不再响应式。
视图渲染触发时机:effect 重新执行
组件 setup 中的模板编译后,会生成一个响应式 effect(由 renderer 创建),它包裹了整个 render 函数。当任意被 track 的响应式属性变化时,对应的 effect 被调度执行,从而重新调用 render,生成新 vnode,再 diff 更新 DOM。
这个过程是同步的(在 nextTick 微任务前),但 DOM 更新本身会延后到 nextTick 队列中批量处理。


















