Vue3 的 Block Tree详细解释(面试)

65 阅读3分钟

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 个子节点:pspanh1button

动态节点只有 2 个:

  • <span>{{ msg }}</span>
  • <button :disabled="isDisabled">

Block Tree 会把动态节点记录成这样:

const dynamicNodes = [
  spanVNode,    // 动态文本
  buttonVNode   // 动态属性
]

会跳过 ph1(静态节点)。

编译器生成的实际结构

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 的三大核心作用

  1. 跳过静态节点 Diff → 大幅减少 Diff 工作量(最主要的性能来源)
  2. 更高效的 children diff(不再递归全树,只操作动态列表)
  3. 静态提升(Hoist)与 Block Tree 配合:静态节点提升到渲染函数外只创建一次,Diff 时直接跳过

一句话总结(适合面试)

Block Tree 是 Vue3 编译阶段生成的“动态节点索引树”,运行时 Diff 只遍历动态节点,大幅减少无效比对,是 Vue3 性能提升的核心技术之一。