首页 / Vue3 入门教程 / 插槽(上)

Vue3 入门教程

插槽(上)

本教程共 40 篇 · 第 17 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

VueVue3 入门教程插槽Slot命名插槽作用域

17. 插槽(上)

本节目标:理解插槽的概念和基础用法,学会用默认插槽和命名插槽让组件更灵活。

插槽解决了什么问题

Props 能传数据,事件能传消息。但如果你想传一段 HTML 结构呢?

比如一个 <FancyButton> 组件,外层样式固定,但内部文字或图标由使用者决定。这时候就需要插槽。

插槽(Slot)就是组件内部的”占位符”(Placeholder)——父组件往里面填充内容,子组件决定在哪里渲染。

默认插槽

子组件里放一个 <slot> 标签作为出口:

<!-- FancyButton.vue -->
<template>
  <button class="fancy-btn">
    <!-- 插槽出口:父组件的内容会渲染在这里 -->
    <slot></slot>
  </button>
</template>

父组件把内容塞进去:

<FancyButton>
  Click me!
</FancyButton>

渲染结果:

<button class="fancy-btn">Click me!</button>

用 JavaScript 函数来类比:

// 父组件调用
FancyButton('Click me!')

// 子组件渲染
function FancyButton(slotContent) {
  return `<button class="fancy-btn">${slotContent}</button>`
}

插槽内容可以是任何模板——文本、元素、甚至其他组件:

<FancyButton>
  <span style="color:red">Click me!</span>
  <AwesomeIcon name="plus" />
</FancyButton>

后备内容

如果父组件没传内容,<slot> 里可以放默认值:

<!-- SubmitButton.vue -->
<button type="submit">
  <!-- 后备内容:父组件没传内容时显示 "Submit" -->
  <slot>Submit</slot>
</button>

使用时:

<!-- 没传内容,显示 "Submit" -->
<SubmitButton />

<!-- 传了内容,显示 "Save" -->
<SubmitButton>Save</SubmitButton>
Tip

后备内容为空时才显示。如果传了空格或空字符串,后备内容不会渲染。

命名插槽(Named Slots)

一个组件可以有多个插槽,用 name 区分:

<!-- BaseLayout.vue -->
<div class="container">
  <header>
    <!-- 命名插槽:父组件通过 #header 指定内容 -->
    <slot name="header"></slot>
  </header>
  <main>
    <!-- 默认插槽(default):未指定名字的内容会到这里 -->
    <slot></slot>
  </main>
  <footer>
    <!-- 命名插槽:父组件通过 #footer 指定内容 -->
    <slot name="footer"></slot>
  </footer>
</div>

父组件用 <template v-slot:name> 指定内容去向。v-slot 有简写 #

<BaseLayout>
  <template #header>
    <h1>页面标题</h1>
  </template>

  <template #default>
    <p>主要内容区域</p>
  </template>

  <template #footer>
    <p>页脚信息</p>
  </template>
</BaseLayout>

没写名字的 <template> 或直接在组件标签下的内容,会进入默认插槽:

<BaseLayout>
  <template #header>
    <h1>页面标题</h1>
  </template>

  <!-- 以下都是默认插槽的内容 -->
  <p>段落一</p>
  <p>段落二</p>
</BaseLayout>
Note

组件标签下所有非 <template> 的顶级节点,自动归入默认插槽。

渲染作用域(Render Scope)

插槽内容在父组件里定义,所以它能访问父组件的数据,不能访问子组件的数据:

<!-- ✅ 能访问父组件的 message(插槽内容继承父组件作用域) -->
<FancyButton>{{ message }}</FancyButton>

<!-- ❌ 不能访问子组件内部的数据 -->

这就像函数的作用域——定义在哪,就只能访问哪里的变量。

条件插槽(Conditional Slots)

有时候你想判断某个插槽是否传了内容,可以用 $slots

<template>
  <div class="card">
    <!-- 判断 header 插槽是否有内容,有才渲染外层 -->
    <div v-if="$slots.header" class="card-header">
      <slot name="header"></slot>
    </div>
    
    <!-- 判断 default 插槽是否有内容,有才渲染外层 -->
    <div v-if="$slots.default" class="card-content">
      <slot></slot>
    </div>
    
    <!-- 判断 footer 插槽是否有内容,有才渲染外层 -->
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer"></slot>
    </div>
  </div>
</template>

这样只有传了内容的位置才会渲染外层包装。

动态插槽名

v-slot 支持动态参数:

<base-layout>
  <template #[dynamicSlotName]>
    动态指定的插槽
  </template>
</base-layout>
Warning

动态参数有语法约束——空格和引号不允许出现在表达式里。

本节回顾

  • 插槽是组件内部的占位符,让父组件填充内容
  • <slot> 是出口,没名字的叫默认插槽
  • 命名插槽用 name 属性区分,父组件用 #name 指定
  • 插槽内容只能访问父组件的作用域
  • 后备内容为空插槽提供默认值