一文看懂 Vue 编译器里的插槽处理逻辑(buildSlots.ts)

Vue 的"插槽"是组件之间传递内容的桥梁。比如你写了这样的模板:

xml 复制代码
<MyCard>
  <template v-slot:header>标题</template>
  <template v-slot:footer>底部</template>
</MyCard>

渲染时,Vue 要把这些内容传给组件内部 <slot name="header"><slot name="footer"> 的地方。

而在编译阶段,Vue 就要把模板里的插槽结构变成一段"可执行的代码" 。这段工作,正是 buildSlots() 函数负责的。


一、先看整体思路:Vue 编译器要做的事情

在模板编译阶段,Vue 会把模板转成"抽象语法树(AST)",然后一层层地分析。
buildSlots() 的任务就是:

  1. 找出组件上和 <template> 上的插槽定义;
  2. 把每个插槽变成一个"渲染函数";
  3. 判断插槽是静态的还是动态的;
  4. 最后生成一个统一的 "slots 对象"------Vue 运行时用它来渲染插槽内容。

所以编译后,大致就会变成这样👇:

css 复制代码
{
  header: () => [createTextVNode('标题')],
  footer: () => [createTextVNode('底部')],
  _: 1 // 插槽标识,用于优化
}

二、核心逻辑讲解

我们一步步来看它在做什么(你不用太关注每个函数名,只看思路)。


1️⃣ 判断有没有插槽

ini 复制代码
const onComponentSlot = findDir(node, 'slot', true)

这行意思是:

👉 "看看这个组件本身有没有 v-slot 属性",比如:

ini 复制代码
<MyCard v-slot="{ msg }">内容</MyCard>

如果有,那它就说明开发者在组件本身定义了一个插槽

于是 Vue 就会创建一个"默认插槽函数":

less 复制代码
slotsProperties.push(
  createObjectProperty(
    arg || createSimpleExpression('default', true),
    buildSlotFn(exp, undefined, children, loc),
  ),
)

翻译成人话就是:

"把插槽名(默认是 default)和渲染函数(children)组成一个对象属性"。


2️⃣ 找出 <template v-slot:name> 的插槽

Vue 允许写在组件内部的 <template v-slot:xxx>,比如:

xml 复制代码
<MyCard>
  <template v-slot:header>标题</template>
  <template v-slot:footer>底部</template>
</MyCard>

编译器会循环组件的所有子节点,找到这些 <template>

c 复制代码
if (isTemplateNode(slotElement) && (slotDir = findDir(slotElement, 'slot', true))) {
  // 获取插槽名和作用域参数
  const { arg: slotName, exp: slotProps } = slotDir
  const slotFunction = buildSlotFn(slotProps, vFor, slotChildren, slotLoc)

  // 把这个插槽函数加到 slots 对象里
  slotsProperties.push(createObjectProperty(slotName, slotFunction))
}

简单解释:

  • slotName → 插槽名称,比如 "header"、"footer";
  • slotFunction → 一个生成 VNode 的函数;
  • 最后组合成 { header: fn1, footer: fn2 } 这样的结构。

3️⃣ 处理带 v-ifv-for 的动态插槽

Vue 允许你写:

arduino 复制代码
<template v-slot:header v-if="showHeader">标题</template>
<template v-slot:item v-for="i in list">Item {{ i }}</template>

这时,编译器要做的就复杂一点:

  • 如果有 v-if,编译成"条件表达式";
  • 如果有 v-for,编译成"循环渲染函数"。

比如下面这段逻辑:

less 复制代码
if (vIf = findDir(slotElement, 'if')) {
  dynamicSlots.push(
    createConditionalExpression(
      vIf.exp!,
      buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++),
      defaultFallback
    )
  )
}

可以理解为:

"如果有 v-if,就生成一个 条件 ? 有这个插槽 : undefined 的表达式。"

而带 v-for 的则会用 RENDER_LIST 来创建循环渲染逻辑。


4️⃣ 处理默认插槽

如果组件没有命名插槽(只有普通子节点),例如:

xml 复制代码
<MyCard>内容</MyCard>

那就会自动创建一个叫 default 的插槽:

scss 复制代码
if (!hasTemplateSlots) {
  slotsProperties.push(buildDefaultSlotProperty(undefined, children))
}

效果是:

{ default: () => [createTextVNode('内容')] }


5️⃣ 给插槽打上"优化标记"

Vue 编译后会给插槽对象添加一个 _ 属性,用于标识类型:

ini 复制代码
const slotFlag = hasDynamicSlots
  ? SlotFlags.DYNAMIC
  : hasForwardedSlots(node.children)
    ? SlotFlags.FORWARDED
    : SlotFlags.STABLE

三种情况:

标识 含义
STABLE (1) 静态插槽,没变
DYNAMIC (2) 动态插槽,依赖变量
FORWARDED (3) 透传插槽(子组件继续传)

这样 Vue 运行时在 diff 时就能做优化,不用每次都重新比对所有插槽。


三、最后生成的结果长什么样?

比如我们有:

xml 复制代码
<MyCard>
  <template v-slot:header>标题</template>
  <template v-slot:footer>底部</template>
</MyCard>

编译后,大致会变成(伪代码):

css 复制代码
const slots = {
  header: () => [createTextVNode('标题')],
  footer: () => [createTextVNode('底部')],
  _: 1 // STABLE
}

如果有动态条件,就会变成:

php 复制代码
createSlots(slots, [
  showHeader ? { name: 'header', fn: ... } : undefined
])

四、为什么要这样设计?

Vue 3 的编译器比 Vue 2 更灵活,它不再简单拼字符串,而是"构建 AST → 生成函数",这带来了几个好处:

  • ✅ 性能更好(静态插槽跳过 diff)
  • ✅ 支持复杂条件和循环
  • ✅ 插槽作用域变量(v-slot="{ foo }") 自动追踪
  • ✅ 支持嵌套组件 slot 转发

五、总结一句话

buildSlots() 的任务,就是"把模板里的各种插槽写法,全部变成一个统一的 slots 对象 ",

并在编译阶段就确定哪些是静态的、哪些需要动态生成。

这个对象就是 Vue 运行时渲染插槽的"蓝图"。


🧩 延伸阅读

如果你有兴趣深入了解,可以配合阅读这几个辅助函数:

  • trackSlotScopes():追踪作用域插槽的变量;
  • trackVForSlotScopes():处理带 v-for 的插槽作用域;
  • buildDynamicSlot():专门用来创建动态插槽结构。

结尾说明:

本文部分内容借助 AI 辅助生成,并由作者整理审核。

相关推荐
gyx_这个杀手不太冷静4 小时前
高级前端开发职业规划(2026—2035)
前端·面试·agent
程序员黑豆4 小时前
Java中的null与NullPointerException完全指南:安全处理、实战排查与面试题
java·前端·ai编程
编程快车4 小时前
用 Phaser 4 造一个空间解谜游戏:纯函数棋盘引擎、透明画布分层与轻量双语
前端·程序员
可视之道4 小时前
前端图形编辑器的多选框架:从单选到框选到 Shift 连选
前端
3秒一个大4 小时前
JS 数组去重全方案:从基础到通用万能去重函数
前端·javascript
阿黎梨梨5 小时前
读懂一个 Next.js 全栈笔记应用
前端
古茗前端团队5 小时前
代码越改越乱?来试试前端领域模型驱动设计(DDD)
前端·agent·ai编程
Hilaku5 小时前
为什么跳槽涨薪 30% 的时代彻底结束了?聊聊 2026 前端薪资的天花板
前端·javascript·程序员
愚公搬代码5 小时前
【愚公系列】《Web应用安全》006-HackBar插件的使用
前端·安全
超爱吃香菜的菜鸟5 小时前
关于pnpm 部分使用(一)
前端·vue.js