首页 / Vue3 入门教程 / 渲染机制

Vue3 入门教程

渲染机制

本教程共 40 篇 · 第 35 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

VueVue3 入门教程Virtual DOM渲染机制编译器优化Patch Flags

35. 渲染机制

本节目标:搞懂 Vue 怎么把模板变成真实 DOM,以及它为什么能高效更新。

虚拟 DOM 是什么

虚拟 DOM(Virtual DOM)是用 JavaScript 对象描述真实 DOM 节点的概念。每个虚拟节点(vnode)长这样:

const vnode = {
  type: 'div',
  props: {
    id: 'hello'
  },
  children: [
    /* 更多 vnode */
  ]
}

一个 vnode 就是普通 JS 对象,包含了创建真实 DOM 需要的所有信息。虚拟 DOM 树就是这些 vnode 嵌套组成的树。

为什么要用虚拟 DOM?因为它让你用声明式的方式构建 UI——你只描述”页面应该长什么样”,至于怎么操作 DOM,交给渲染器去处理。

渲染器的两个核心操作:

  • Mount:遍历虚拟 DOM 树,创建真实 DOM 节点
  • Patch:比较新旧两棵虚拟 DOM 树,只更新有变化的部分

Vue 的渲染流程

一个 Vue 组件从挂载到更新,经历了三个阶段:

1. 编译

Vue 模板被编译成渲染函数——一个返回虚拟 DOM 树的函数。

这个编译步骤可以在构建时(AOT)提前完成,也可以在浏览器里运行时编译。生产环境一般用构建时编译。

2. Mount

运行时调用渲染函数,拿到虚拟 DOM 树,然后创建真实 DOM。这个过程是响应式的——Vue 会追踪所有用到了的响应式依赖。

3. Patch

当响应式依赖变化时,渲染函数重新执行,生成新的虚拟 DOM 树。然后把新旧两棵树做对比,只把变化应用到真实 DOM。

模板 vs 渲染函数

模板最终也会编译成渲染函数。那为什么不直接写渲染函数?

模板的优势:

模板渲染函数
接近 HTML,易读易写需要嵌套调用
便于 CSS 和设计师协作有学习成本
能做静态分析,编译器可优化编译器无法优化

渲染函数的优势:

模板渲染函数
灵活性有限完全用 JS 控制
受限于模板语法任意逻辑都可以

日常开发用模板就够了。渲染函数只在高度动态的场景才需要——比如根据运行时配置生成不同结构的组件。

编译器告知的虚拟 DOM

React 等框架的虚拟 DOM 是纯运行时的:每次 re-render 都要全量遍历树、逐个 diff 属性。即使某部分永远不会变,也得重新创建 vnode。

Vue 不同。Vue 同时控制编译器和运行时,编译器可以在编译阶段分析模板,给运行时留下”提示信息”。运行时拿到这些提示后可以跳过很多不必要的工作。

这就是”编译器告知的虚拟 DOM”(Compiler-Informed Virtual DOM)。

优化一:静态缓存

模板里没有动态绑定的部分:

<div>
  <div>foo</div> <!-- 静态 -->
  <div>bar</div> <!-- 静态 -->
  <div>{{ dynamic }}</div>
</div>

foobar 永远不会变。编译器标记它们为静态,渲染器创建 vnode 后缓存起来,后续 re-render 直接复用同一个 vnode,完全跳过 diff。

连续的静态节点会被合并成一个”静态 vnode”,直接通过 innerHTML 插入,连 vnode 都不用创建。

优化二:Patch Flags

对于有动态绑定的元素,编译器在生成代码时会带上一个”补丁标记”:

createElementVNode("div", {
  class: _normalizeClass({ active: _ctx.active })
}, null, 2 /* CLASS */)

最后的 2 就是 Patch Flag,表示”这个元素只需要更新 class”。

运行时用位运算检查标记:

if (vnode.patchFlag & PatchFlags.CLASS) {
  // 只更新 class,其他属性完全不管
}

常见的 Patch Flag:

Note

Patch Flag 具体数值为内部实现细节,日常开发中由模板编译器自动生成,无需手动记忆。

标记含义
1动态文本内容
2动态 class
4动态 style
8动态属性(除 class/style)
64稳定子节点顺序的 fragment
128需要移动子节点的 fragment

位运算极快。有了 Patch Flag,Vue 更新时只做最少的 diff 工作。

优化三:树拍平(Tree Flattening)

编译器把模板分成一个个”block”——没有 v-ifv-for 等结构指令的区域就是一个 block。

每个 block 内部追踪所有带 Patch Flag 的后代节点,拍平成一个数组:

<div> <!-- root block -->
  <div>...</div>         <!-- 不追踪 -->
  <div :id="id"></div>   <!-- 追踪 -->
  <div>                  <!-- 不追踪 -->
    <div>{{ bar }}</div> <!-- 追踪 -->
  </div>
</div>

re-render 时只遍历这个扁平数组,跳过所有静态节点。静态区域越多,优化越明显。

v-ifv-for 会创建新的 block 节点,每个 block 内部独立追踪动态节点。

对 SSR 的优化

Patch Flags 和树拍平同样优化服务端渲染的 hydration 过程:

  • 根据 Patch Flag 走快速 hydration 路径
  • 只遍历 block 和动态节点,实现”局部 hydration”

本节回顾

  • 虚拟 DOM 是用 JS 对象描述 DOM 节点的技术
  • Vue 渲染流程:编译 → Mount → Patch
  • 模板编译成渲染函数,日常开发推荐模板
  • Vue 通过编译器给运行时提供优化提示
  • 静态缓存跳过不变节点的 diff
  • Patch Flag 告诉运行时该更新什么
  • 树拍平把 block 内动态节点拍平成数组,re-render 只遍历数组