Vue插槽更新复用旧vnode并仅更新上下文,避免重建与重渲染;普通插槽返回静态vnode,作用域插槽通过函数动态生成,更新依赖父组件重渲染。

Vue.js 的 patch 函数在组件更新阶段对插槽内容的处理,核心在于“不直接 diff 插槽 vnode,而是复用旧的插槽 vnode 并仅更新其依赖的数据上下文”。这避免了无谓的 VNode 重建和 DOM 重渲染,是 Vue 高效更新的关键设计之一。
插槽 vnode 在 patch 前已被预编译并缓存
组件实例挂载时,父组件的插槽内容(即 内部的模板)会被编译为函数(scopedSlots 或 slots),并在子组件 render 中调用生成插槽 vnode。这些 vnode 在首次渲染后被缓存在组件实例的 _renderedVNodes 或 slots 对象中,并关联到当前作用域(如 scope、data、parent 等)。
- 普通插槽(
slots.default)返回的是静态 vnode 数组,不带响应式上下文 - 作用域插槽(
scopedSlots.xxx)返回的是函数,每次调用都会基于最新 props / data 重新生成 vnode -
patch过程中不会重新执行插槽函数,除非插槽定义本身变化(如父组件重新 render)
patch 时跳过插槽 vnode 的深度 diff
当子组件触发更新(例如 props 变更),patch 对比新旧 vnode 时,若发现旧 vnode 的 children 中包含插槽内容(即由 createFunctionalComponent 或 resolveSlots 生成的 vnode),会直接跳过对其子节点的递归 diff。
- 判断依据是 vnode 的
isSlot标记或fnContext/fnScopeId等插槽专属字段 - 只要插槽函数未变更(
oldVnode.fn === vnode.fn)、且作用域数据未导致插槽函数引用变化,就复用旧 vnode 节点 - 真正更新发生在后续的
patchVNode阶段:只更新 vnode 的data、context、slotScope等运行时属性,不重建整个结构
作用域插槽更新依赖父组件重渲染
作用域插槽的内容是否更新,不取决于子组件自身,而取决于父组件是否重新执行了插槽函数。也就是说:
- 子组件内部
props改变 → 触发子组件patch→ 复用插槽 vnode,但可能更新其context(如绑定的新props) - 父组件中插槽模板内用了响应式数据 → 该数据变化 → 父组件 re-render → 重新执行
scopedSlots.xxx()→ 生成新 vnode → 子组件patch时检测到插槽函数返回值变化 → 替换插槽 vnode - 若插槽函数是箭头函数或闭包中捕获了过期响应式对象,可能导致插槽内容未及时更新(常见于
v-for中错误地将插槽函数定义在循环内)
手动强制更新插槽内容的方法
多数情况下无需干预,但特殊场景(如动态切换插槽实现、或插槽依赖外部非响应式状态)可主动触发更新:
- 在子组件中调用
this.$forceUpdate()→ 强制父组件重新执行插槽函数(前提是父组件也响应式地参与了渲染) - 给插槽函数添加唯一 key,如
:key="slotKey",改变slotKey促使 Vue 认为插槽已更换 - 使用
v-if控制插槽容器显隐,间接触发插槽 vnode 的销毁与重建 - 避免在插槽函数中直接读取
this.xxx(子组件实例),应通过props显式传入所需数据
Vue 的插槽更新策略本质是“最小化 vnode 重建 + 上下文感知复用”,既保障响应性,又守住性能底线。理解这一点,能更好规避插槽不更新、重复渲染、闭包 stale state 等典型问题。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











