Vue3 的 SFC 编译优化之 Block 和 PatchFlag 协同工作原理
字数 1166 2025-11-12 07:34:22

Vue3 的 SFC 编译优化之 Block 和 PatchFlag 协同工作原理

1. 问题背景

Vue3 的编译优化核心目标是减少运行时虚拟 DOM 的对比开销。传统 Diff 算法需要递归比较整个虚拟 DOM 树,而 Vue3 通过编译时的静态分析,结合 BlockPatchFlag 标记,实现靶向更新(只对比动态内容)。

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-ifv-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. 协同工作流程

编译阶段

  1. 扫描模板,为动态节点标记 patchFlag
  2. 将包含动态节点的父节点升级为 Block,并递归收集其下所有动态子节点到 dynamicChildren
  3. 静态节点会被提升(Hoist Static),避免重复渲染。

运行时更新阶段

  1. 当组件更新时,只需对比 Block 的 dynamicChildren 数组,而非整个树。
  2. 遍历 dynamicChildren,根据每个节点的 patchFlag 执行对应的更新操作:
    • patchFlag 包含 TEXT,仅更新文本内容;
    • 若包含 CLASS,仅对比 class 属性。
  3. 静态节点完全跳过对比。

性能优势

  • 扁平化的动态节点数组避免了递归遍历。
  • patchFlag 使更新逻辑细化,减少不必要的属性对比。

5. 特殊处理:v-if 与 v-for

  • v-if 分支:每个分支被视为一个独立的 Block,因为分支结构可能变化。
  • v-for:整个列表作为一个 Block,内部每个项为动态节点,通过 Fragment 管理。

6. 总结

BlockPatchFlag 的协同实现了编译时优化 + 运行时靶向更新

  • Block 通过 dynamicChildren 缩小 Diff 范围。
  • PatchFlag 通过标识更新类型减少对比粒度。
  • 两者结合使 Vue3 的虚拟 DOM 性能接近手动优化代码。
Vue3 的 SFC 编译优化之 Block 和 PatchFlag 协同工作原理 1. 问题背景 Vue3 的编译优化核心目标是减少运行时虚拟 DOM 的对比开销。传统 Diff 算法需要递归比较整个虚拟 DOM 树,而 Vue3 通过编译时的静态分析,结合 Block 和 PatchFlag 标记,实现 靶向更新 (只对比动态内容)。 2. 核心概念:PatchFlag PatchFlag 是编译阶段为动态节点添加的标识,用二进制位表示节点的更新类型。例如: 编译时,编译器会分析模板中的动态绑定,为虚拟 DOM 节点添加 patchFlag 属性。 示例模板编译结果 : 编译后的虚拟 DOM 结构类似: 3. Block 的作用 Block 是一个特殊的虚拟 DOM 节点,它负责收集其内部的 所有动态子节点 (包括嵌套节点)。 动态节点 :带有 patchFlag 的节点,或包含动态子节点的节点(如 v-if 、 v-for 包裹的节点)。 Block 的 dynamicChildren 属性 :编译阶段将当前 Block 下的所有动态子节点扁平化收集到该数组中。 示例 : 编译后外层 div 会变为一个 Block: 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 性能接近手动优化代码。