Block Tree 是 Vue 3 编译器生成的块级更新信息图,通过 v-memo 显式声明依赖边界、分离循环源与 UI 状态、用 template v-for 按类型拆分,实现嵌套循环的精准局部更新。

Block Tree 是 Vue 3 响应式系统与编译优化协同工作的核心结构,它不是用户直接操作的 API,而是编译器在构建阶段生成的“块级更新信息图”。利用它实现局部更新,本质是让模板编译产出更细粒度、更独立的更新块(block),从而避开嵌套循环中因响应式依赖粗放导致的整块重渲染问题。
理解 Block Tree 的分块逻辑
Vue 3 模板编译时会将模板划分为多个嵌套的 Dynamic Node Block(动态节点块),每个 block 包含一组具有相同响应式依赖集合的节点。循环(v-for)默认被编译为一个大 block —— 这意味着只要循环内任一数据变化,整个列表都会触发 patch;而 Block Tree 的优化目标,就是把可独立更新的部分(如单个列表项内部的文本、状态图标)拆成子 block。
- 根 block 对应整个组件,粒度最粗
- 带
v-for的<li>默认是一个 block,但若其内部有独立响应式状态(如item.isExpanded),且该状态不随循环源数组变更,就具备拆分子 block 的条件 - 编译器不会自动为每个
{{ item.name }}单独建 block,除非你用v-memo或显式包裹动态内容
用 v-memo 显式声明稳定依赖边界
v-memo 是 Vue 3.2+ 提供的指令,它告诉编译器:“这个元素及其子树的更新,只依赖于我列出的这些值;只要它们不变,整个子树跳过 diff”。它直接作用于 Block Tree 的分块决策,是解决嵌套循环局部更新最轻量、最可控的方式。
例如,渲染一个支持展开/收起的评论列表:
<div v-for="comment in comments" :key="comment.id">
<h3>{{ comment.author }}</h3>
<p>{{ comment.content }}</p>
<button @click="toggleExpand(comment.id)">
{{ comment.isExpanded ? '收起' : '展开' }}
</button>
<div v-memo="[comment.isExpanded]" v-show="comment.isExpanded">
<details>...大量扩展内容</details>
</div>
</div>这里 v-memo="[comment.isExpanded] 让扩展区域成为一个独立 block:点击按钮只改变 isExpanded,Vue 只需比对并更新该 block,完全跳过 <h3> 和 <p> 的 diff。
避免响应式污染:分离循环源与 UI 状态
嵌套循环卡顿常源于“状态混杂”——比如把 expandedIds: Set 存在循环源数组里,或直接在 comments 数组对象上添加 isExpanded 字段。这会导致每次调用 toggleExpand() 都触发整个 v-for block 的依赖追踪刷新。
- ✅ 推荐:用外部响应式 Map 或 ref 管理 UI 状态
const expandedMap = ref(new Map())
切换时仅修改 Map,不影响comments响应式依赖链 - ✅ 结合
v-memo使用:v-memo="[expandedMap.value.has(comment.id)]" - ❌ 避免:
comment.isExpanded = !comment.isExpanded(污染原始响应式数据)
必要时手动控制 block 边界:用 <template v-for> + key 分离
当循环项结构差异大(如混合了评论、广告、推荐卡片),可不用单一 v-for,改用多个 <template v-for> 并赋予不同 key,引导编译器生成更合理的 block 层级:
<!-- 每类内容独立 block,互不影响 -->
<template v-for="item in filteredComments" :key="`comment-${item.id}`">
<CommentItem :data="item" />
</template>
<p><template v-for="ad in ads" :key="<code>ad-${ad.id}</code>">
<AdBanner :data="ad" />
</template>相比全塞进一个 v-for,这种写法让编译器更容易为每类组件生成专属 block,减少跨类型更新干扰。

















