vue 插槽


一、基本插槽(默认插槽)

父组件传递内容

vue 复制代码
<!-- Parent.vue -->
<template>
  <ChildComponent>
    <!-- 这里的内容会被插入到子组件的 slot 位置 -->
    <p>这是从父组件传入的内容</p>
  </ChildComponent>
</template>

子组件定义插槽位置

vue 复制代码
<!-- ChildComponent.vue -->
<template>
  <div class="card">
    <h2>卡片标题</h2>
    <!-- slot 就是内容的占位符 -->
    <slot></slot>
  </div>
</template>

渲染结果

html 复制代码
<div class="card">
  <h2>卡片标题</h2>
  <p>这是从父组件传入的内容</p>
</div>

二、具名插槽

当一个组件有多个插槽时,需要用名字来区分。

子组件定义多个命名插槽

vue 复制代码
<!-- Layout.vue -->
<template>
  <div class="layout">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <!-- 没有 name 的就是默认插槽 -->
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

父组件使用具名插槽

vue 复制代码
<!-- App.vue -->
<template>
  <Layout>
    <!-- v-slot 指令指定插槽名,简写为 # -->
    <template #header>
      <h1>页面标题</h1>
    </template>

    <!-- 默认插槽不需要指定名称 -->
    <p>主要内容区域</p>

    <template #footer>
      <p>版权信息 © 2026</p>
    </template>
  </Layout>
</template>

语法说明

  • v-slot:header 可简写为 #header
  • 默认插槽可以省略 template 标签,直接写在组件标签内
  • 一个组件只能有一个默认插槽

三、作用域插槽

让父组件能访问子组件内部的数据。

子组件向插槽传递数据

vue 复制代码
<!-- List.vue -->
<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      <!-- 通过 v-bind 将数据绑定到 slot 上 -->
      <slot :item="item" :index="index" :isEven="index % 2 === 0">
        <!-- 默认显示内容,当父组件未提供插槽内容时显示 -->
        {{ item }}
      </slot>
    </li>
  </ul>
</template>

<script setup>
const props = defineProps({
  items: Array
})
</script>

父组件接收并使用插槽数据

vue 复制代码
<!-- Parent.vue -->
<template>
  <List :items="['苹果', '香蕉', '橘子']">
    <!-- 通过 v-slot 接收子组件传来的数据 -->
    <template #default="{ item, index, isEven }">
      <span :class="{ even: isEven }">
        {{ index + 1 }}. {{ item }}
      </span>
    </template>
  </List>
</template>

解构语法#default="{ item, index }" 可以直接解构出需要的属性,也可以设置默认值 #default="{ item = '未知' }"


四、动态插槽名

Vue 3 支持用变量作为插槽名称。

vue 复制代码
<template>
  <BaseLayout>
    <template #[dynamicSlotName]>
      动态插槽内容
    </template>
  </BaseLayout>
</template>

<script setup>
import { ref } from 'vue'
const dynamicSlotName = ref('header')
</script>

五、插槽的默认内容

可以在 <slot> 标签内放置默认内容,当父组件没有提供插槽内容时显示。

vue 复制代码
<!-- Button.vue -->
<template>
  <button class="btn">
    <slot>提交</slot>  <!-- 默认显示"提交" -->
  </button>
</template>
vue 复制代码
<!-- 使用1:不传内容,显示默认 -->
<MyButton />  <!-- 渲染为 <button>提交</button> -->

<!-- 使用2:传入内容,覆盖默认 -->
<MyButton>保存</MyButton>  <!-- 渲染为 <button>保存</button> -->

六、Vue 3 相比 Vue 2 的变化

1. 统一了 slotslot-scope 语法

Vue 2 中有两套语法(slot / slot-scope 属性和 v-slot 指令),Vue 3 只保留 v-slot 指令。

2. 移除 $scopedSlots

Vue 2 中需要通过 this.$scopedSlots 访问作用域插槽,Vue 3 统一合并到 this.$slots

3. 渲染函数中的变化

javascript 复制代码
// Vue 2
h('div', [
  this.$scopedSlots.default({ text: 'hello' })
])

// Vue 3
h('div', [
  this.$slots.default({ text: 'hello' })
])

七、实际应用示例:表格列自定义

vue 复制代码
<!-- DataTable.vue -->
<template>
  <table>
    <thead>
      <tr>
        <th v-for="col in columns" :key="col.key">{{ col.title }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in data" :key="row.id">
        <td v-for="col in columns" :key="col.key">
          <!-- 允许用户自定义某列的渲染方式 -->
          <slot :name="`cell-${col.key}`" :row="row" :value="row[col.key]">
            {{ row[col.key] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script setup>
defineProps({
  columns: Array,
  data: Array
})
</script>
vue 复制代码
<!-- 使用表格并自定义操作列 -->
<DataTable :columns="columns" :data="users">
  <template #cell-action="{ row }">
    <button @click="editUser(row)">编辑</button>
    <button @click="deleteUser(row)">删除</button>
  </template>
</DataTable>

总结:Vue 3 的插槽机制更加简洁统一,作用域插槽让父子组件间的数据传递更加灵活,非常适合构建可复用的通用组件库。掌握插槽是写好 Vue 组件的重要基础。