Patch Flags(补丁标记)
Vue 3 模板编译器为动态节点生成的位标记常量(如 TEXT=1, CLASS=2),运行时 diff 通过位运算跳过不可能变化的属性,将 diff 复杂度从 O(template) 压缩到 O(dynamic bindings)。本质上是编译器给渲染管线留的"快捷通道"。
核心概念
Patch Flags 是 Vue 3 模板编译器在编译阶段为每个动态 VNode 生成的位标记(bitmask)。它告诉运行时 diff 算法"这个节点的哪些部分是动态的",使 patchElement 函数能通过位运算直接跳过静态属性的比对。
这是典型的编译器-运行时协作:编译器在 createElementVNode 的第四个参数位置注入 Patch Flag,运行时读取并据此选择最短的 diff 路径。
常量定义
源码位置:packages/shared/src/patchFlags.ts
ts
export const enum PatchFlags {
TEXT = 1, // 动态文本内容
CLASS = 1 << 1, // 2 — 动态 class
STYLE = 1 << 2, // 4 — 动态 style
PROPS = 1 << 3, // 8 — 动态非 class/style 的 props
FULL_PROPS = 1 << 4, // 16 — 有动态 key,需全量 diff
NEED_HYDRATION = 1 << 5, // 32 — 需要水合的事件监听
STABLE_FRAGMENT = 1 << 6, // 64 — 子节点顺序不变
KEYED_FRAGMENT = 1 << 7, // 128 — 有 key 的 fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 — 无 key 的 fragment
NEED_PATCH = 1 << 9, // 512 — 需非 props patch(ref/指令)
DYNAMIC_SLOTS = 1 << 10, // 1024 — 动态插槽
DEV_ROOT_FRAGMENT = 1 << 11, // 2048 — 仅开发环境
HOISTED = -1, // 静态提升节点,diff 直接跳过
BAIL = -2 // 优化退出,走全量比较
}
编译示例
模板:
html
<div :class="cls">static text</div>
编译产物:
js
createElementVNode("div", { class: _ctx.cls }, "static text", 2 /* CLASS */)
末尾的 2 即 PatchFlags.CLASS。运行时 patchElement 检测到此标记后,只比对 class 属性,textContent 已知是静态的直接跳过。
运行时使用方式
renderer.ts 的 patchElement 内部通过位运算判断:
js
if (patchFlag & PatchFlags.CLASS) {
// 只 diff class
}
if (patchFlag & PatchFlags.STYLE) {
// 只 diff style
}
多个动态属性通过位或组合:一个同时有动态 class 和 style 的节点,patchFlag = CLASS | STYLE = 6。
配合机制
- Static Hoisting(静态提升):
HOISTED = -1标记的 VNode 被提升到渲染函数外部,每次渲染复用同一个引用,diff 时直接跳过。 - Block Tree:编译器将模板按
v-if/v-for切分为 Block,每个 Block 只追踪内部的动态节点(带 Patch Flag 的),静态节点不进入 diff 流程。 - Hydration 加速:Patch Flags 在水合路径上价值尤其大——
hydrateElement根据标记只检查动态属性是否与服务端一致,静态部分直接信任,大幅减少水合比对工作量。
局限性
Patch Flags 仅对模板语法生效。Vue 中使用 JSX 编写的渲染函数不会获得 Patch Flags 优化,因为 JSX 的动态性无法在编译期静态分析——这是 Vue 模板和 JSX 两种渲染模式优化程度不一致的根本原因。
引用本术语的文章