Vue 的响应式系统,本质上是一套自动化的依赖追踪机制。与传统轮询方案需要定时检查或手动触发更新不同,它通过数据读取与变更的“自然联动”,在恰当的时机静默介入。整个过程的核心在于两个关键步骤:渲染函数执行时收集依赖,以及响应式数据被修改时通知重渲染。

首次挂载:渲染函数执行 → 自动收集依赖
组件首次渲染时,Vue 会调用 render 函数生成虚拟 DOM。此过程中,所有被读取的响应式数据(如 ref.value 或 reactive.obj.name)都会触发 getter。如果当前存在活跃的副作用(例如组件的 render effect),该 effect 会被自动记录到对应响应式属性的依赖池中,完成依赖收集。
这里有几个细节值得注意:
- 嵌套访问,比如
state.user.profile.avatar,会逐层触发 getter,实现深度依赖的记录。 - 模板中写的
{{ count }},底层等价于 render 函数里访问count.value,同样会完成依赖收集。
数据变更:setter 被调用 → 触发更新队列
当响应式数据被修改(如 count.value++ 或 state.msg = 'new')时,setter 或 Proxy 的 set 拦截器会触发,随后启动更新流程:
- 根据变更的字段(比如
'msg'),定位对应的依赖池。 - 遍历依赖池中所有已登记的 Watcher 或 effect,包括组件的渲染 effect。
- 将它们推入异步更新队列,该队列基于 microtask 机制,确保同一事件循环内不会重复执行。
- 待队列清空后,批量重新执行相关的 render 函数,生成新的虚拟 DOM,并进行 diff 更新。
父组件更新:props 变更 → 子组件可能重渲染
子组件是否重渲染,完全取决于它是否实际使用了发生变化的 props。如果模板或 render 函数中读取了某个 prop,比如 {{ title }},那么该 prop 的变更会触发子组件的依赖收集链路,进而重跑 render。反之,如果 props 改变了,但子组件没有在渲染逻辑中读取它,就不会触发更新——Vue 不会做无差别的刷新。
即使子组件的 data 没有变化,只要父组件传递的响应式 prop 被读取且发生了变更,子组件仍然会走完整的更新流程。这一点是 Vue 响应式系统的核心优势之一。
其他触发场景:非数据变更也能间接驱动
有些操作本身不修改数据,但会打破响应式链路或强制刷新:
this.$forceUpdate():跳过依赖追踪,直接标记组件为“需要更新”,适用于新增未劫持属性后的补救场景。triggerRef():针对shallowRef主动唤醒依赖,常用于内部状态需要手动通知的场景。- keep-alive 组件激活时:虽然数据没有变化,但 Vue 会重新执行 render,确保视图与缓存状态一致。
