目录
[二、插槽的 4 种类型](#二、插槽的 4 种类型)
[Q:v-slot 的简写是什么?](#Q:v-slot 的简写是什么?)
Q:使用插槽时,子组件和父组件都要用template包裹吗?
[Q:Vue 2 和 Vue 3 的插槽有什么区别?](#Q:Vue 2 和 Vue 3 的插槽有什么区别?)
[Q: 具名插槽和作用域插槽的 v-slot 有什么不一样?](#Q: 具名插槽和作用域插槽的 v-slot 有什么不一样?)
[Q:多个元素填同一个插槽必须要用 template 吗?](#Q:多个元素填同一个插槽必须要用 template 吗?)
一、什么是插槽?
一句话:插槽就是组件里留的"坑位",让父组件可以往里面填内容。
┌─────────────────────────────────────────────────────────────┐
│ 卡片组件(Card) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ┌─────────────────────────────────────────────────┐│ │
│ │ │ 插槽:放什么就显示什么 ││ │
│ │ └─────────────────────────────────────────────────┘│ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 父组件使用: │
│ <Card>这里的内容会出现在插槽位置</Card> │
└─────────────────────────────────────────────────────────────┘
二、插槽的 4 种类型
| 类型 | 说明 | 使用场景 |
|---|---|---|
| 默认插槽 | 一个坑位,放默认内容 | 简单的内容插入 |
| 具名插槽 | 多个坑位,每个有名字 | 复杂布局(头部、内容、底部) |
| 作用域插槽 | 子组件数据传给父组件 | 父组件需要子组件的数据来渲染 |
| 动态插槽 | 插槽名可以动态变化 | 根据条件显示不同插槽 |
三、默认插槽
子组件(Card.vue)
javascript
<template>
<div class="card">
<div class="card-header">卡片标题</div>
<div class="card-body">
<!-- 默认插槽:父组件放什么,这里就显示什么 -->
<slot></slot>
</div>
</div>
</template>
父组件使用
javascript
<template>
<Card>
<!-- 这些内容会填入默认插槽 -->
<p>这是卡片的内容</p>
<button>点击按钮</button>
</Card>
</template>
渲染结果:
javascript
<div class="card">
<div class="card-header">卡片标题</div>
<div class="card-body">
<p>这是卡片的内容</p>
<button>点击按钮</button>
</div>
</div>
四、具名插槽
当组件有多个位置需要填充时,使用具名插槽。
子组件(Layout.vue)
javascript
<template>
<div class="layout">
<header>
<!-- 名字叫 header 的插槽 -->
<slot name="header">默认头部</slot>
</header>
<main>
<!-- 默认插槽 -->
<slot></slot>
</main>
<footer>
<!-- 名字叫 footer 的插槽 -->
<slot name="footer">默认底部</slot>
</footer>
</div>
</template>
父组件使用
html
<template>
<Layout>
<!-- 具名插槽:用 # 或 v-slot -->
<template #header>
<h1>自定义头部</h1>
</template>
<!-- 默认插槽 -->
<p>主要内容区域</p>
<template #footer>
<p>自定义底部</p>
</template>
</Layout>
</template>
渲染结果:
html
<div class="layout">
<header><h1>自定义头部</h1></header>
<main><p>主要内容区域</p></main>
<footer><p>自定义底部</p></footer>
</div>
五、作用域插槽
子组件可以把数据传给父组件,让父组件决定如何渲染。
子组件(UserList.vue)
html
<template>
<ul>
<li v-for="user in users" :key="user.id">
<!-- 把 user 数据传给父组件 -->
<slot name="user" :user="user" :index="index">
<!-- 默认显示方式 -->
{{ user.name }}
</slot>
</li>
</ul>
</template>
<script setup>
const users = [
{ id: 1, name: '张三', age: 18 },
{ id: 2, name: '李四', age: 20 },
{ id: 3, name: '王五', age: 22 }
]
</script>
父组件使用
html
<template>
<UserList>
<!-- 作用域插槽:可以拿到子组件传来的 user 数据 -->
<template #user="{ user, index }">
<div class="user-card">
<span>{{ index + 1 }}.</span>
<strong>{{ user.name }}</strong>
<span>({{ user.age }}岁)</span>
</div>
</template>
</UserList>
</template>
渲染结果:
html
<ul>
<li><div class="user-card"><span>1.</span><strong>张三</strong><span>(18岁)</span></div></li>
<li><div class="user-card"><span>2.</span><strong>李四</strong><span>(20岁)</span></div></li>
<li><div class="user-card"><span>3.</span><strong>王五</strong><span>(22岁)</span></div></li>
</ul>
六、动态插槽
插槽名可以根据变量动态变化。
html
<template>
<Layout>
<!-- 动态插槽名 -->
<template #[slotName]>
动态插槽内容
</template>
</Layout>
</template>
<script setup>
import { ref } from 'vue'
const slotName = ref('header')
// 可以动态切换:'header'、'content'、'footer'
</script>
七、完整示例:弹窗组件
html
<!-- Modal.vue -->
<template>
<div v-if="visible" class="modal">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
<h3>默认标题</h3>
</slot>
<button class="close" @click="handleClose">×</button>
</div>
<div class="modal-body">
<!-- 默认插槽:放内容 -->
<slot>默认内容</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button @click="handleClose">取消</button>
<button @click="handleConfirm">确定</button>
</slot>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps(['visible'])
const emit = defineEmits(['update:visible', 'confirm'])
function handleClose() {
emit('update:visible', false)
}
function handleConfirm() {
emit('confirm')
handleClose()
}
</script>
html
<!-- 父组件使用 -->
<template>
<button @click="showModal = true">打开弹窗</button>
<Modal v-model:visible="showModal" @confirm="handleConfirm">
<template #header>
<h3 style="color: red;">删除确认</h3>
</template>
<p>确定要删除这条数据吗?</p>
<p class="warning">此操作不可恢复!</p>
<template #footer>
<button class="cancel" @click="showModal = false">我再想想</button>
<button class="confirm" @click="handleConfirm">确认删除</button>
</template>
</Modal>
</template>
八、插槽对比总结
| 插槽类型 | 写法(子组件) | 写法(父组件) | 数据流向 |
|---|---|---|---|
| 默认插槽 | <slot></slot> |
直接放内容 | 父→子 |
| 具名插槽 | <slot name="xxx"> |
<template #xxx> |
父→子 |
| 作用域插槽 | <slot :data="data"> |
<template #default="{ data }"> |
子→父 |
九、面试高频问题
Q:插槽是什么?有什么作用?
答:
插槽是 Vue 组件中预留的"坑位",让父组件可以向子组件注入内容。它让组件更灵活、更可复用。
作用:
组件内容可定制
提高组件的复用性
实现复杂的布局组件
Q:具名插槽和作用域插槽的区别?
答:
具名插槽:给插槽起名字,父组件可以往指定位置填内容(数据从父→子)
作用域插槽:子组件可以把数据传给父组件,让父组件决定如何渲染(数据从子→父)
Q:v-slot 的简写是什么?
答:
v-slot:header可以简写为#header
Q:使用插槽时,子组件和父组件都要用template包裹吗?
答:不是。
子组件 :用
<slot>标签定义插槽,不需要<template>包裹父组件:填充插槽时,根据情况决定:
单个元素填默认插槽:可以不用
<template>多个元素填同一个插槽:需要用
<template>包裹填具名插槽或作用域插槽:需要用
<template #插槽名>
Q:插槽可以有默认内容吗?
可以。
<slot>默认内容</slot>,如果父组件不传内容,就显示默认内容。
Q:默认插槽和具名插槽可以混用吗?
可以。组件里可以有多个具名插槽和一个默认插槽。
Q:作用域插槽和具名插槽可以混用吗?
可以。作用域插槽可以有名字。
Q:作用域插槽有什么实际应用场景?
比如一个表格组件,子组件提供数据,父组件可以自定义每一列的显示方式(文本、按钮、图片等)。或者一个列表组件,父组件可以自定义每个列表项的样式。
Q:Vue 2 和 Vue 3 的插槽有什么区别?
Vue 3 中
v-slot只能用在<template>上,不能用在普通元素上。写法上更规范了。
Q: 具名插槽和作用域插槽的 v-slot 有什么不一样?
答:
两者都是
v-slot,但作用不同、写法不同:
具名插槽 :用于匹配子组件中指定名字的插槽,不接收数据 。写法是
#header或v-slot:header作用域插槽 :用于接收子组件传过来的数据,父组件可以自定义渲染方式。写法是
#default="{ data }"或#item="{ item }"核心区别:具名插槽是父组件告诉子组件"内容放哪里";作用域插槽是子组件告诉父组件"这里有数据,你来决定怎么显示"。
Q:多个元素填同一个插槽必须要用 template 吗?
答:是的。
对于默认插槽:
单个元素可以不用
<template>,直接写多个元素必须用
<template>包裹对于具名插槽 和作用域插槽:
- 无论单个还是多个元素,都必须用
<template>包裹这是因为 Vue 只能传递单个根节点给插槽,用
<template>可以把多个元素组合成一个整体。