Vue 插槽(Slot)的本质是位置占位 + 内容分发。它解决了组件复用中“结构相似但内容不同”的痛点,
避免了通过大量 Props 传递 HTML 字符串导致的维护困难和 XSS 风险。
以下是默认插槽、具名插槽和作用域插槽的核心用法与实战指南。
1. 默认插槽(Default Slot)
核心场景:组件只有一个主要区域需要自定义,或者作为其他插槽的“兜底”内容。
子组件(挖坑):使用
父组件(填坑):直接在子组件标签内部写入 HTML 结构。
<!-- 子组件 BaseCard.vue -->
<template>
<div class="card">
<div class="card-body">
<!-- 如果父组件没传内容,显示“暂无数据” -->
<slot>暂无数据</slot>
</div>
</div>
</template>
<!-- 父组件 App.vue -->
<template>
<BaseCard>
<p>这是父组件传入的自定义内容</p>
</BaseCard>
<BaseCard />
<!-- 显示默认后备内容:“暂无数据” -->
</template>
2. 具名插槽(Named Slots)
核心场景:一个组件内有多个区域需要独立定制(如弹窗的头部、主体、底部)。
子组件(挖多坑):给<slot> 添加 name 属性区分不同区域。
父组件(对号入座):使用 <template v-slot:名字> 或简写 <template #名字>。
注意:v-slot 指令只能用在 <template> 标签或组件根标签上,
不能直接用在普通 HTML 标签(如 <div>)上。
<!-- 子组件 Modal.vue -->
<template>
<div class="modal">
<header>
<slot name="header">默认标题</slot>
</header>
<main>
<slot></slot> <!-- 匿名插槽,等价于 name="default" -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<!-- 父组件 App.vue -->
<template>
<Modal>
<!-- 简写语法 #header 等价于 v-slot:header -->
<template #header>
<h3>订单详情</h3>
</template>
<!-- 默认插槽内容,无需 template 包裹也可,但推荐包裹以保持清晰 -->
<p>订单具体内容...</p>
<template #footer>
<button>取消</button>
<button>确认</button>
</template>
</Modal>
</template>
3. 作用域插槽(Scoped Slots)
核心场景:子组件拥有数据,但父组件决定如何渲染这些数据。常见于表格列定制、列表项自定义渲染。
子组件(传数据):在 <slot> 标签上通过绑定属性(:prop="data")将内部数据暴露出去。
父组件(收数据):通过 v-slot="params" 接收数据对象,并通过解构 { prop } 直接使用。
<!-- 子组件 UserList.vue -->
<template>
<ul>
<li v-for="user in users" :key="user.id">
<!-- 将 user 对象暴露给父组件 -->
<slot :user="user" :index="user.id">
{{ user.name }} <!-- 默认渲染方式 -->
</slot>
</li>
</ul>
</template>
<script setup>
const users = [
{ id: 1, name: '张三', role: 'Admin' },
{ id: 2, name: '李四', role: 'User' }
]
</script>
<!-- 父组件 App.vue -->
<template>
<UserList>
<!-- 接收子组件传来的数据,解构出 user -->
<template #default="{ user }">
<span class="name">{{ user.name }}</span>
<span class="badge" v-if="user.role === 'Admin'">管理员</span>
</template>
</UserList>
</template>
快速避坑与最佳实践
语法限制:v-slot 必须写在 <template> 标签上(除非是独占默认插槽且无其他具名插槽时可直接写在组件标签上,
但为了统一规范,建议始终使用 <template>)。
名称匹配:具名插槽的 name 必须严格匹配。
子组件定义 name="header",父组件必须用 #header 或 v-slot:header。
解构命名:作用域插槽中,父组件接收的参数名(如 { user })可以是任意合法的变量名,只要对应子组件绑定的属性名即可。例如子组件传 :item="data",父组件可以写 v-slot="{ item }"。
默认内容优先级:如果父组件提供了插槽内容,子组件< slot> 标签内的默认内容将被完全替换;如果父组件未提供,则显示子组件的默认内容。
选型指南
| 插槽类型 | 核心能力 | 典型场景 |
|---|---|---|
| 默认插槽 | 单区域内容分发 | 按钮文本、卡片主体、简单容器 |
| 具名插槽 | 多区域独立定制 | 弹窗(头/体/脚)、页面布局、复杂表单 |
| 作用域插槽 | 子传父数据 + 自定义渲染 | 表格列自定义、列表项渲染、动态图表 |
