Vue3 的 SFC 编译优化之 Block 和 PatchFlag 协同工作原理
字数 1166 2025-11-12 07:34:22
Vue3 的 SFC 编译优化之 Block 和 PatchFlag 协同工作原理
1. 问题背景
Vue3 的编译优化核心目标是减少运行时虚拟 DOM 的对比开销。传统 Diff 算法需要递归比较整个虚拟 DOM 树,而 Vue3 通过编译时的静态分析,结合 Block 和 PatchFlag 标记,实现靶向更新(只对比动态内容)。
2. 核心概念:PatchFlag
PatchFlag 是编译阶段为动态节点添加的标识,用二进制位表示节点的更新类型。例如:
export const enum PatchFlags {
TEXT = 1, // 动态文本(如 {{ value }})
CLASS = 2, // 动态 class
STYLE = 4, // 动态 style
PROPS = 8, // 动态属性(非 class/style)
// ... 其他标志
}
编译时,编译器会分析模板中的动态绑定,为虚拟 DOM 节点添加 patchFlag 属性。
示例模板编译结果:
<div :class="{ active }">{{ text }}</div>
编译后的虚拟 DOM 结构类似:
{
type: 'div',
props: { class: { active } },
children: [ { type: Text, content: text } ],
patchFlag: 3 // 二进制 1 | 2:文本和 class 需更新
}
3. Block 的作用
Block 是一个特殊的虚拟 DOM 节点,它负责收集其内部的所有动态子节点(包括嵌套节点)。
- 动态节点:带有
patchFlag的节点,或包含动态子节点的节点(如v-if、v-for包裹的节点)。 - Block 的
dynamicChildren属性:编译阶段将当前 Block 下的所有动态子节点扁平化收集到该数组中。
示例:
<div>
<span>{{ a }}</span>
<p :class="cls">{{ b }}</p>
<div v-if="ok">{{ c }}</div>
</div>
编译后外层 div 会变为一个 Block:
const block = {
type: 'div',
dynamicChildren: [
{ type: 'span', children: a, patchFlag: 1 },
{ type: 'p', props: { class: cls }, children: b, patchFlag: 3 },
{ type: 'div', children: c, patchFlag: 1 } // v-if 整体视为动态
]
}
4. 协同工作流程
编译阶段:
- 扫描模板,为动态节点标记
patchFlag。 - 将包含动态节点的父节点升级为 Block,并递归收集其下所有动态子节点到
dynamicChildren。 - 静态节点会被提升(Hoist Static),避免重复渲染。
运行时更新阶段:
- 当组件更新时,只需对比 Block 的
dynamicChildren数组,而非整个树。 - 遍历
dynamicChildren,根据每个节点的patchFlag执行对应的更新操作:- 若
patchFlag包含TEXT,仅更新文本内容; - 若包含
CLASS,仅对比 class 属性。
- 若
- 静态节点完全跳过对比。
性能优势:
- 扁平化的动态节点数组避免了递归遍历。
patchFlag使更新逻辑细化,减少不必要的属性对比。
5. 特殊处理:v-if 与 v-for
- v-if 分支:每个分支被视为一个独立的 Block,因为分支结构可能变化。
- v-for:整个列表作为一个 Block,内部每个项为动态节点,通过
Fragment管理。
6. 总结
Block 和 PatchFlag 的协同实现了编译时优化 + 运行时靶向更新:
- Block 通过
dynamicChildren缩小 Diff 范围。 - PatchFlag 通过标识更新类型减少对比粒度。
- 两者结合使 Vue3 的虚拟 DOM 性能接近手动优化代码。