跳到主要内容

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-oncekeep-alive 的缓存策略等影响(要结合具体场景判断)。