Vue 组件渲染和更新过程
面试速答(30 秒版 TL;DR)
- 渲染:组件
render(或模板编译后的 render)生成 VNode 树 -> 走 patch 生成/更新真实 DOM。 - 依赖收集:渲染时读取响应式数据,框架把“当前渲染/副作用函数”记录为依赖。
- 更新:响应式数据变更 -> 触发依赖 -> 组件更新被调度(批处理)-> 重新 render 生成新 VNode -> diff/patch 更新 DOM。
- 关键:更新不是“立刻改 DOM”,而是 调度 + 批处理(同一 tick 合并多次变更)。
统一心智模型(Vue 2 / Vue 3 都适用)
可以把 Vue 的更新讲成:响应式系统负责“谁依赖谁”,渲染器负责“怎么把 VNode 变成 DOM”。
更细一点:关键节点(面试追问常见)
1) 模板到渲染函数
.vue template会被编译为render()。- 运行时执行
render()得到 VNode(虚拟 DOM)。
2) 为什么能“知道要更新哪个组件”
因为渲染阶段会读取响应式数据:
- Vue 2:依赖收集基于
Object.defineProperty的 getter/setter + watcher。 - Vue 3:依赖收集基于
Proxy+effect(副作用函数)+track/trigger。
渲染副作用就是“组件更新函数”,所以数据变更能精确触发相关组件更新。
3) 批处理(nextTick 的根因)
同一事件循环中多次 state 变更会合并:
- 更新任务进队列
- 微任务/宏任务时机统一 flush
你在业务里看到的现象通常是:
- 你连续改了多次响应式数据,DOM 不会每次都立刻同步更新。
- 需要在 DOM 更新后读布局信息时,用
nextTick()等待本轮更新完成。
import { nextTick, ref } from 'vue';
const n = ref(0);
async function incAndMeasure() {
n.value++;
await nextTick();
// 此时 DOM 已经反映 n 的变化,适合读高度/宽度等
}
diff/patch 会比较什么(简答即可)
- 组件更新后拿到 “旧 VNode” 和 “新 VNode”,做 同层级 diff,尽量复用已有 DOM。
- 列表渲染需要稳定的
key:让框架更准确地复用与移动节点,避免状态错位。
常见追问
Q1:哪些操作会触发组件更新?
- 模板/render 里读取过的响应式数据发生变化。
- 父组件更新导致子组件 props 变化(或者父组件重渲染引起子组件重新 patch)。
Q2:为什么有时候改了数据但页面没更新?
常见原因:
- 读写的不是响应式数据(例如解构丢失响应式、或直接改了非响应式对象)。
- Vue 2 里给对象新增属性、数组某些下标修改等需要用特定方式(
Vue.set等)才能触发。 - 组件被
v-once、keep-alive的缓存策略等影响(要结合具体场景判断)。