Vue3 的 Block Tree(区块树)
下面讲清楚为什么 Block Tree 能让 Vue3 的 Diff 比 React 快。
什么是 Block Tree
Block Tree 是 Vue3 编译器生成的一种“只记录动态节点的节点树”,让运行时 Diff 不需要遍历所有子节点,只遍历可能发生变化的动态节点。
也就是说:
- 真实 DOM 树可能有 100 个节点
- 但只有 5 个是动态的
- Block Tree 会只记录这 5 个
因此运行时只 diff 这 5 个,不再遍历 100 个,性能显著提升。
为什么需要 Block Tree
Vue2/React 的 Diff 都要:
- 遍历所有子节点
- 逐个判断是否变化
- 再决定
patch
但 Vue3 的 template 是编译的,它可以提前知道哪些部分是动态的,于是可以只记录动态节点,而忽略静态节点。
示例:Block Tree 长什么样
假设我们有一个模板:
<div>
<p>静态内容</p>
<span>{{ msg }}</span>
<h1 class="title">静态标题</h1>
<button :disabled="isDisabled">Click</button>
</div>
DOM 节点树有 4 个子节点:p、span、h1、button
动态节点只有 2 个:
<span>{{ msg }}</span><button :disabled="isDisabled">
Block Tree 会把动态节点记录成这样:
const dynamicNodes = [
spanVNode, // 动态文本
buttonVNode // 动态属性
]
会跳过 p 和 h1(静态节点)。
编译器生成的实际结构
Vue3 编译后,每一个元素会变成一个“Block”对象:
{
type: 'div',
dynamicChildren: [
/* 只有动态节点 */
],
children: [
/* 全部 vnode,但 patch 时不会遍历这个,只遍历 dynamicChildren */
]
}
关键字段:
children:包含所有子节点(完整树)dynamicChildren:只包含“动态子节点”(Block Tree 核心)
Diff 时:
children不再深度遍历- 只遍历
dynamicChildren
Block Tree 的工作流程
Diff 时的简化流程:
patchElement(oldVNode, newVNode)
↓
if dynamicChildren 存在
只 diff dynamicChildren
else
fallback 到普通 children diff
这意味着 Vue3 在大多数情况下根本不会检查静态节点,甚至不会递归 children。
对比 React:
- React 必须遍历所有 props 和所有 children,因为 JSX 无编译信息
- Vue3 可以直接跳过静态部分
复杂度差异(有无 Block Tree)
假设有 1000 个子节点,其中只有 10 个是动态的:
- React / Vue2 Diff:需要遍历 1000 个节点(含静态)
- Vue3 Block Tree Diff:只遍历 10 个动态节点
这就是 Vue3 为什么比 React 更快的原因之一。
为什么叫 Block
- 一个根节点代表一个“块”(Block)
- 块内部可以有子块(嵌套 Block)
- 每个块自己维护自己的动态节点列表
最终形成树状结构:Block Tree。
Block Tree 的三大核心作用
- 跳过静态节点 Diff → 大幅减少 Diff 工作量(最主要的性能来源)
- 更高效的 children diff(不再递归全树,只操作动态列表)
- 静态提升(Hoist)与 Block Tree 配合:静态节点提升到渲染函数外只创建一次,Diff 时直接跳过
一句话总结(适合面试)
Block Tree 是 Vue3 编译阶段生成的“动态节点索引树”,运行时 Diff 只遍历动态节点,大幅减少无效比对,是 Vue3 性能提升的核心技术之一。