Vue 模板编译原理

Vue 模板如何变成可执行的 render function?

Vue 模板编译的核心流程可以概括为:

template
→ Parse
→ AST
→ Transform
→ Codegen
→ render function
→ VNode

1. 为什么需要模板编译?

浏览器不能直接理解 Vue 的模板语法:

<template>
  <button :class="{ active: enabled }" @click="toggle">
    {{ label }}
  </button>
</template>

Vue 需要把模板转换成 JavaScript render function。组件运行时执行 render function,生成 VNode,再由 renderer 将 VNode patch 到真实 DOM。

2. Parse:模板解析为 AST

Parse 阶段会读取模板字符串,识别:

  • 元素节点;
  • 文本和插值节点;
  • 属性和指令;
  • 注释;
  • 条件、循环等结构。

例如:

<div>{{ message }}</div>

会被解析成类似这样的 AST:

Element(div)
└── Interpolation(message)

AST 是后续分析和生成代码的中间表示。

3. Transform:分析和优化 AST

Transform 阶段会遍历 AST,将模板语义转换成运行时需要的信息,例如:

  • 普通元素转换为 VNode 创建调用;
  • v-if 转换为条件表达式;
  • v-for 转换为列表渲染逻辑;
  • 插值转换为读取响应式状态的表达式;
  • 事件和属性转换为 props;
  • 标记静态节点、动态属性和动态子树。

Vue 3 会在这里生成 Patch Flag、Block 等优化信息,让运行时只关注真正可能变化的部分。

4. Codegen:生成 render function

<div class="box">{{ message }}</div>

最终会生成近似下面的逻辑:

function render(_ctx) {
  return h("div", { class: "box" }, _ctx.message);
}

实际 Vue 3 编译产物会使用 openBlockcreateElementBlock 等内部函数,并附带动态节点标记。这里的代码只是帮助理解的简化版本。

5. 编译时优化

静态提升

不会变化的 VNode 或 props 可以提升到 render function 外部,避免每次渲染重复创建。

Patch Flag

Patch Flag 标记的是单个 VNode 内部哪些内容可能变化,例如只有文本变化、只有 class 变化,或只有部分 props 变化。

<div :class="{ active }">{{ name }}</div>

这个 div 本身是动态节点,编译器可以给它加上类似 TEXT | CLASS 的标记。运行时 patch 到这个节点时,就不需要完整比较所有属性和子节点,只需要更新文本和 class

Block Tree

Block Tree 解决的是更新时如何快速找到动态节点。如果每次组件更新都从根节点递归遍历整棵 VNode 树,即使动态节点带了 Patch Flag,也仍然要穿过大量静态层级。

<div>
  <header>固定标题</header>
  <main>
    <span>{{ name }}</span>
  </main>
  <footer>固定页脚</footer>
</div>

这里真正会变的只有 span。Vue 3 会让当前 Block 收集动态后代,形成类似 dynamicChildren = [span] 的结构。组件更新时可以直接遍历这组动态节点,跳过 headermainfooter 这些静态层级。

所以两者的区别是:

  • Block Tree:决定“这次更新需要看哪些节点”,避免遍历整棵树;
  • Patch Flag:决定“看到了某个动态节点后,需要更新它的哪些部分”,避免完整比较节点内容。

它们通常配合使用:Block Tree 先把运行时带到动态节点面前,Patch Flag 再告诉运行时这个节点应该怎么精确更新。

事件处理函数缓存

部分静态事件处理器可以被缓存,减少每次 render 创建新函数的成本。

6. 运行时编译与构建时编译

  • 运行时编译:浏览器中携带模板编译器,运行时把 template 编译为 render function,包体更大。
  • 构建时编译:在构建阶段完成编译,线上只保留运行时,通常体积更小、启动更快。

现代 Vue 项目通常使用 SFC 编译器在构建阶段处理 <template>

7. 常见误区

  • Vue 模板不是直接转换成 HTML 字符串,而是通常转换成 render function。
  • 编译优化不能替代正确使用 key 和合理的组件边界。
  • Patch Flag 是运行时提示,不是跳过所有 Diff。
  • 模板编译与响应式系统是两条链路:编译负责生成渲染代码,响应式负责触发重新渲染。

面试总结

Vue 模板会先经过 Parse 生成 AST,再经过 Transform 完成指令转换和静态分析,最后由 Codegen 生成 render function。运行时执行 render function 生成 VNode,renderer 再通过 patch 更新 DOM。Vue 3 还通过静态提升、Patch Flag 和 Block Tree 缩小运行时更新范围。