Skip to content

✨ 插槽 👌

要点速览

  • 插槽用于“父组件传递模板内容”给子组件,传的是结构而非数据。
  • 分类:默认插槽与具名插槽(name),具名可使用 v-slot:name#name
  • 支持动态插槽名:v-slot:[slotName]#[slotName]
  • 作用域插槽:子组件在 <slot> 上绑定属性,父组件通过 v-slot 接收(可解构)。
  • 作用域规则:父模板只访问父作用域;子模板只访问子作用域。
  • 常见模式:无渲染组件(只提供逻辑,视图由插槽决定)。

快速上手

子组件定义默认与具名插槽,父组件填充不同内容:

vue
<!-- components/Card.vue -->
<template>
  <div class="card">
    <div class="card-header">
      <slot name="header"></slot>
    </div>
    <div class="card-body">
      <slot></slot>
    </div>
  </div>
</template>

<script setup></script>

<style scoped>
.card {
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  width: 300px;
  margin: 20px;
}
.card-header {
  background-color: #f7f7f7;
  border-bottom: 1px solid #ececec;
  padding: 10px 15px;
  font-size: 16px;
  font-weight: bold;
  border-top-left-radius: 8px;
  border-top-right-radius: 8px;
}
.card-body {
  padding: 15px;
  font-size: 14px;
  color: #333;
}
</style>
vue
<!-- 父组件 -->
<template>
  <div>
    <Card>
      <template v-slot:header>我的卡片标题</template>
      这是卡片的内容
    </Card>
    <Card>
      <template #header>探险摄影</template>
      <div>
        <img src="./assets/landscape.jpeg" class="card-image" />
        <p>探索未知的自然风光,记录每一个令人惊叹的瞬间。</p>
      </div>
    </Card>
  </div>
</template>

<script setup>
import Card from "./components/Card.vue";
</script>

<style scoped>
.card-image {
  width: 100%;
  height: auto;
  border-bottom: 1px solid #ececec;
}
</style>

插槽作用域与渲染

  • 插槽内容在父组件作用域中渲染;无法直接访问子组件的数据。
  • 子组件只负责“占位”,视图内容由父组件提供并在父作用域计算。

插槽相关细节

默认内容

若父组件未提供内容,默认插槽会渲染其内部默认值:

vue
<slot>这是默认插槽的默认值</slot>

具名插槽与简写

vue
<template v-slot:header>探险摄影</template>
<!-- 简写 -->
<template #header>探险摄影</template>

当同时接收默认与具名插槽时,位于顶级的非 template 节点的内容会进入默认插槽。

动态插槽名

vue
<template v-slot:[slotName]>探险摄影</template>
<!-- 简写 -->
<template #[slotName]>探险摄影</template>

作用域插槽(向上暴露数据)

子组件通过在 <slot> 上绑定属性向父组件暴露数据,父组件以 v-slot 接收:

vue
<!-- 子组件 MyComponent.vue -->
<template>
  <div>
    <slot :text="greetingMessage" :count="1"></slot>
  </div>
</template>
vue
<!-- 父组件接收 -->
<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

也可在父层解构:

vue
<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>

作用域与响应性

  • slotProps 来自子组件,是按照父作用域进行渲染的只读“输入”。
  • 一般不在父层直接修改 slotProps,需要交互时使用 Props / 事件 / v-model

具名作用域插槽

当需要同时暴露多个数据或区分不同区域时,推荐使用具名作用域插槽:

vue
<!-- 子组件:在具名插槽上绑定数据 -->
<template>
  <ul>
    <li v-for="(item, i) in list" :key="item.id">
      <slot name="item" :item="item" :index="i">
        <!-- 父组件未提供时的回退内容 -->
        {{ item.defaultText }}
      </slot>
    </li>
  </ul>
</template>
vue
<!-- 父组件:使用具名作用域插槽并解构接收 -->
<List>
  <template #item="{ item, index }">
    <span>{{ index + 1 }}. {{ item.name }}</span>
  </template>
</List>

也支持动态具名作用域插槽:

vue
<!-- 父组件:动态插槽名,并解构接收 -->
<List>
  <template v-slot:[slotName]="{ item }">
    <strong>{{ item.name }}</strong>
  </template>
</List>

使用建议

  • 具名插槽适合复杂结构或多区域输出(如 header、footer、item)。
  • 父层必须用 template 包裹具名插槽内容,便于传入复杂结构和解构 slot props
  • 保持插槽名一致;#itemname="item" 必须匹配。

实战:列表与无渲染组件

子组件通过具名插槽把每项数据暴露给父组件,父组件决定具体渲染:

vue
<!-- 子组件 List.vue -->
<template>
  <div class="list-container">
    <ul>
      <li v-for="item in items" :key="item.id">
        <slot name="item" :item="item">{{ item.defaultText }}</slot>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from "vue";
const items = ref([
  {
    id: 1,
    name: "Vue.js",
    defaultText: "Vue.js 是一个渐进式 JavaScript 框架。",
  },
  {
    id: 2,
    name: "React",
    defaultText: "React 是一个用于构建用户界面的 JavaScript 库。",
  },
  {
    id: 3,
    name: "Angular",
    defaultText: "Angular 是一个开源的 Web 应用框架。",
  },
]);
</script>

<style>
.list-container {
  max-width: 300px;
  background: #f9f9f9;
  border: 1px solid #ccc;
  padding: 20px;
  border-radius: 8px;
}
ul {
  list-style: none;
  padding: 0;
}
li {
  margin-bottom: 10px;
  background: #fff;
  padding: 10px;
  border-radius: 6px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
</style>
vue
<!-- 父组件使用 -->
<template>
  <div class="app-container">
    <List>
      <template #item="{ item }">
        <h3>{{ item.name }}</h3>
        <p>{{ item.defaultText }}</p>
      </template>
    </List>
  </div>
</template>

<script setup>
import List from "@/components/List.vue";
</script>

<style>
.app-container {
  padding: 20px;
}
</style>

无渲染组件

  • 有些组件只包含逻辑,不直接输出视图;视图由作用域插槽交给使用者决定。
  • 这种模式提高了可复用性和灵活性(列表、表格、图表容器等)。

与 Props / 事件 / v-model 的关系

  • 插槽传的是“结构与渲染上下文”,不负责数据双向传输。
  • 要传数据用 Props;要向上通知用事件;要受控双向用 v-model
  • 插槽常与 Props 同用:通过 Props 控制可见性/状态,通过插槽定制内容。

常见误区

  • 在插槽内容中直接访问子组件数据,会因作用域限制而报错或为空。
  • 忘记在父层使用 template 包裹具名插槽,导致无法传入复杂结构。
  • 将插槽误当作数据通道;应使用 Props / 事件 / v-model 完成数据交互。
  • 动态插槽名书写不规范(方括号遗漏),导致插槽无法匹配。

小结与后续

插槽让组件在保持封装的同时具备强大的内容定制能力:

  1. 先掌握默认/具名/动态插槽的用法与作用域规则。
  2. 再熟悉作用域插槽,把子组件的数据以插槽 Props 暴露给父层渲染。
  3. 最后与 Props/事件/v-model 组合,构建灵活的可复用组件。

学习建议

  • 建议配合阅读「Props」「自定义事件」「组件 v-model」形成完整心智模型。
  • 从一个列表或卡片组件开始实践具名与作用域插槽,逐步提炼为无渲染组件。