Block Tree 通过编译期动态性分析决定自定义组件是否成为独立更新块,关键在于其是否引入响应式依赖边界或动态上下文;具名/作用域插槽影响粒度,v-memo可进一步细化;嵌套中仅实际消费响应式依赖的组件节点开启新Block。

Block Tree 本身不直接“处理”自定义组件,而是通过编译阶段对组件调用位置的动态性分析,决定是否将其划入独立更新块(Block),从而间接控制其更新时机和范围。关键不在组件本身,而在它是否引入新的响应式依赖边界或动态上下文。
自定义组件作为 Block 边界:天然隔离更新链路
当一个自定义组件被渲染时,Vue 3 编译器会检查它的 props 绑定、插槽内容、以及是否使用了 v-if / v-for 等指令。只要存在响应式绑定(如 :title="user.name" 或 v-if="showCard"),该组件节点就会成为新的 Block 边界——这意味着:
- 它的子树(包括内部模板、插槽内容、子孙组件)会被划入一个独立 Block,与父组件的其他动态区域解耦
- 仅当该组件的 props 响应式值变化、或其自身触发更新(如 emit + reactive state 变更),这个 Block 才会重新执行 render 并 diff
- 父组件中其他不相关状态(如侧边栏折叠状态)变化时,不会导致该组件 Block 重运行
插槽内容如何影响组件 Block 的粒度?
插槽是影响自定义组件更新精度的关键变量。Block Tree 对插槽的处理逻辑如下:
-
默认行为:具名插槽或默认插槽的内容由父组件定义,因此其更新依赖收集发生在父组件作用域;若父组件某 ref 变化只影响
#header插槽,则只有该插槽所在的 Block 会刷新,组件主体和其他插槽保持静止 -
作用域插槽:数据来自子组件(如
item),但模板在父组件中。编译器会将插槽函数体单独成块,并仅关联子组件传入的响应式参数(如item.id)。列表项更新时,只会触发对应项的作用域插槽 Block,不会重绘整个组件容器 -
配合 v-memo:可在插槽内容中加
v-memo="[item.id]",强制创建更细粒度的子 Block,避免因父组件其他无关响应式依赖(如全局搜索关键词)误触发插槽重渲染
深层嵌套组件链中的 Block 传递机制
多个自定义组件嵌套(如 A → B → C)并不会让 Block 层级无限加深。编译器按实际依赖聚合节点,而非机械按组件层级拆分:
- 如果组件 B 的所有 props 都稳定(无响应式绑定),且插槽内容也是静态的,那么 B 节点可能被提升为静态节点,不生成独立 Block,其子树直接并入 A 的 Block 中
- 如果组件 C 的某个 prop(如
:loading="isPending")是响应式的,而 isPending 只在顶层组件 A 中定义,那么从 A 到 C 的整条路径上,编译器会在 C 节点处开启新 Block —— 因为这是第一个真正消费该依赖的位置 - Block 不跨组件“穿透”,也不会把 A 的依赖直接注入 C 的 Block;C 的 Block 只关心它直接接收的响应式输入(props + 插槽函数内表达式所依赖的变量)
如何验证组件是否被正确分块?
最直接的方式是使用 Vue Template Explorer 查看编译输出:
- 打开你的模板,粘贴含自定义组件的代码
- 观察生成的 render 函数中是否有类似
openBlock()、createBlock()的调用包裹组件节点 - 若有,说明该组件已被识别为动态边界,拥有独立 Block;若只是
createVNode()调用,则大概率被合并进父 Block 或静态提升

















