一、先明确核心概念
- 具名插槽 :给
<slot>标签添加name属性,用于区分不同位置的插槽,让父组件可以精准地将内容插入到子组件的指定位置,解决「默认插槽只能插入一处内容」的问题。 - 默认插槽 :没有
name属性的<slot>,是具名插槽的特殊形式(默认名称为default),父组件中未指定插槽名称的内容,会默认插入到这里。 - 插槽默认内容 :在子组件的
<slot>标签内部写入内容,当父组件未给该插槽传递任何内容时,会显示这份默认内容;若父组件传递了内容,会覆盖默认内容,提升组件的复用性和容错性。 - 作用域插槽 :子组件通过「属性绑定」的方式给
<slot>传递内部私有数据,父组件在使用插槽时可以接收这些数据并自定义渲染,解决「父组件无法访问子组件内部数据」的问题,实现「子组件供数、父组件定制渲染」。
二、分步实例演示
第一步:实现最基础的「具名插槽 + 默认插槽」
核心需求:创建一个通用的「页面容器组件」,包含「页头」「页面内容」「页脚」三个部分,其中「页面内容」用默认插槽,「页头」「页脚」用具名插槽。
1. 子组件:定义插槽(文件名:PageContainer.vue)
vue
<template>
<!-- 通用页面容器样式(简单美化,方便查看效果) -->
<div class="page-container" style="border: 1px solid #e0e0e0; border-radius: 8px; padding: 20px; margin: 20px 0;">
<!-- 具名插槽:页头(name="header") -->
<div class="page-header" style="border-bottom: 1px dashed #e0e0e0; padding-bottom: 10px; margin-bottom: 10px;">
<slot name="header" />
</div>
<!-- 默认插槽:页面核心内容(无name属性,对应default) -->
<div class="page-content" style="margin: 20px 0; min-height: 100px;">
<slot />
</div>
<!-- 具名插槽:页脚(name="footer") -->
<div class="page-footer" style="border-top: 1px dashed #e0e0e0; padding-top: 10px; margin-top: 10px; text-align: right;">
<slot name="footer" />
</div>
</div>
</template>
<script setup>
// 子组件无需额外逻辑,仅定义插槽结构即可
</script>
2. 父组件:使用插槽(传递内容,文件名:App.vue)
父组件通过 v-slot:插槽名(简写:#插槽名)指定内容要插入的具名插槽,未指定的内容默认插入到默认插槽。
vue
<template>
<h2>基础具名插槽 + 默认插槽演示</h2>
<!-- 使用子组件 PageContainer -->
<PageContainer>
<!-- 给具名插槽 header 传递内容(简写 #header,完整写法 v-slot:header) -->
<template #header>
<h3>这是文章详情页的页头</h3>
<nav>首页 > 文章 > Vue 插槽教程</nav>
</template>
<!-- 未指定插槽名,默认插入到子组件的默认插槽 -->
<div>
<p>1. 具名插槽可以让父组件精准控制内容插入位置。</p>
<p>2. 默认插槽用于承载组件的核心内容,使用更简洁。</p>
<p>3. 这部分内容会显示在页头和页脚之间。</p>
</div>
<!-- 给具名插槽 footer 传递内容(简写 #footer) -->
<template #footer>
<span>发布时间:2026-01-13</span>
<button style="margin-left: 20px; padding: 4px 12px;">收藏文章</button>
</template>
</PageContainer>
</template>
<script setup>
// 导入子组件
import PageContainer from './PageContainer.vue';
</script>
3. 运行效果与说明
- 页头区域显示「文章详情页标题 + 面包屑导航」(对应
#header插槽内容)。 - 中间区域显示核心正文(对应默认插槽内容)。
- 页脚区域显示「发布时间 + 收藏按钮」(对应
#footer插槽内容)。 - 关键:父组件的
<template>标签包裹插槽内容,通过#插槽名绑定子组件的具名插槽,结构清晰,互不干扰。
第二步:实现「带默认内容的插槽」
核心需求:优化上面的 PageContainer.vue,给「页脚插槽」添加默认内容(默认显示「返回顶部」按钮),当父组件未给 footer 插槽传递内容时,显示默认按钮;若传递了内容,覆盖默认内容。
1. 修改子组件:给插槽添加默认内容(PageContainer.vue)
仅修改 footer 插槽部分,在 <slot name="footer"> 内部写入默认内容:
vue
<template>
<div class="page-container" style="border: 1px solid #e0e0e0; border-radius: 8px; padding: 20px; margin: 20px 0;">
<!-- 具名插槽:页头 -->
<div class="page-header" style="border-bottom: 1px dashed #e0e0e0; padding-bottom: 10px; margin-bottom: 10px;">
<slot name="header" />
</div>
<!-- 默认插槽:页面核心内容 -->
<div class="page-content" style="margin: 20px 0; min-height: 100px;">
<slot />
</div>
<!-- 具名插槽:页脚(带默认内容) -->
<div class="page-footer" style="border-top: 1px dashed #e0e0e0; padding-top: 10px; margin-top: 10px; text-align: right;">
<slot name="footer">
<!-- 插槽默认内容:父组件未传递footer内容时,显示该按钮 -->
<button style="padding: 4px 12px;" @click="backToTop">返回顶部</button>
</slot>
</div>
</div>
</template>
<script setup>
// 定义默认内容的点击事件(返回顶部)
const backToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth' // 平滑滚动
});
};
</script>
2. 父组件演示两种场景(App.vue)
分别演示「不传递 footer 内容」和「传递 footer 内容」的效果:
vue
<template>
<h2>带默认内容的插槽演示</h2>
<!-- 场景1:父组件不传递 footer 插槽内容,显示子组件的默认「返回顶部」按钮 -->
<h4>场景1:未传递页脚内容(显示默认按钮)</h4>
<PageContainer>
<template #header>
<h3>这是未传递页脚的页面</h3>
</template>
<p>该页面父组件没有给 footer 插槽传递内容,所以页脚会显示子组件默认的「返回顶部」按钮。</p>
</PageContainer>
<!-- 场景2:父组件传递 footer 插槽内容,覆盖默认按钮 -->
<h4 style="margin-top: 40px;">场景2:传递页脚内容(覆盖默认按钮)</h4>
<PageContainer>
<template #header>
<h3>这是传递了页脚的页面</h3>
</template>
<p>该页面父组件给 footer 插槽传递了自定义内容,会覆盖子组件的默认「返回顶部」按钮。</p>
<template #footer>
<span>作者:Vue 小白教程</span>
<button style="margin-left: 20px; padding: 4px 12px;">点赞</button>
<button style="margin-left: 10px; padding: 4px 12px;">评论</button>
</template>
</PageContainer>
</template>
<script setup>
import PageContainer from './PageContainer.vue';
</script>
3. 运行效果与说明
- 场景1:页脚显示「返回顶部」按钮,点击可实现平滑滚动到页面顶部(默认内容生效)。
- 场景2:页脚显示「作者 + 点赞 + 评论」,默认的「返回顶部」按钮被覆盖(自定义内容生效)。
- 核心价值:插槽默认内容让组件更「健壮」,无需父组件每次都传递所有插槽内容,减少冗余代码,提升组件复用性。
第三步:实际业务场景综合应用(卡片组件)
核心需求:创建一个通用的「商品卡片组件」,使用具名插槽实现「商品图片」「商品标题」「商品价格」「操作按钮」的自定义配置,其中「操作按钮」插槽带默认内容(默认「加入购物车」按钮)。
1. 子组件:商品卡片(GoodsCard.vue)
vue
<template>
<div class="goods-card" style="width: 280px; border: 1px solid #f0f0f0; border-radius: 12px; padding: 16px; margin: 16px; float: left; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<!-- 具名插槽:商品图片 -->
<div class="goods-img" style="width: 100%; height: 180px; margin-bottom: 12px; text-align: center;">
<slot name="image" />
</div>
<!-- 具名插槽:商品标题 -->
<div class="goods-title" style="font-size: 16px; font-weight: 500; margin-bottom: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<slot name="title" />
</div>
<!-- 具名插槽:商品价格 -->
<div class="goods-price" style="font-size: 18px; color: #ff4400; margin-bottom: 16px;">
<slot name="price" />
</div>
<!-- 具名插槽:操作按钮(带默认内容) -->
<div class="goods-actions" style="text-align: center;">
<slot name="action">
<!-- 默认内容:加入购物车按钮 -->
<button style="width: 100%; padding: 8px 0; background: #ff4400; color: #fff; border: none; border-radius: 8px; cursor: pointer;">
加入购物车
</button>
</slot>
</div>
</div>
</template>
<script setup>
// 无需额外逻辑,仅提供插槽结构和默认内容
</script>
2. 父组件:使用商品卡片组件(App.vue)
自定义不同商品的内容,演示插槽的灵活性:
vue
<template>
<h2>实际业务场景:商品卡片组件</h2>
<div style="overflow: hidden; clear: both;">
<!-- 商品1:使用默认操作按钮(加入购物车) -->
<GoodsCard>
<template #image>
<img src="https://picsum.photos/240/180?random=1" alt="商品图片" style="width: 240px; height: 180px; object-fit: cover; border-radius: 8px;">
</template>
<template #title>
小米手机 14 旗舰智能手机
</template>
<template #price>
¥ 4999
</template>
<!-- 未传递 #action 插槽,显示默认「加入购物车」按钮 -->
</GoodsCard>
<!-- 商品2:自定义操作按钮(立即购买 + 收藏) -->
<GoodsCard>
<template #image>
<img src="https://picsum.photos/240/180?random=2" alt="商品图片" style="width: 240px; height: 180px; object-fit: cover; border-radius: 8px;">
</template>
<template #title>
苹果 iPad Pro 平板电脑
</template>
<template #price>
¥ 7999
</template>
<!-- 自定义 #action 插槽内容,覆盖默认按钮 -->
<template #action>
<button style="width: 48%; padding: 8px 0; background: #0071e3; color: #fff; border: none; border-radius: 8px; cursor: pointer; margin-right: 4%;">
立即购买
</button>
<button style="width: 48%; padding: 8px 0; background: #f0f0f0; color: #333; border: none; border-radius: 8px; cursor: pointer;">
收藏
</button>
</template>
</GoodsCard>
</div>
</template>
<script setup>
import GoodsCard from './GoodsCard.vue';
</script>
3. 运行效果与说明
- 商品1:操作按钮显示默认的「加入购物车」,快速实现基础功能。
- 商品2:操作按钮显示「立即购买 + 收藏」,满足自定义需求。
- 业务价值:通过具名插槽,打造了「通用可复用」的商品卡片组件,父组件可以根据不同商品场景,灵活配置各个区域的内容,既减少了重复代码,又保证了灵活性。
第四步:实现「作用域插槽」
核心需求:基于现有商品卡片组件优化,让子组件持有私有商品数据,通过作用域插槽传递给父组件,父组件自定义渲染格式(如给高价商品加「高端」标识、显示商品优惠信息)。
1. 修改子组件:定义作用域插槽,传递内部数据(GoodsCard.vue)
子组件新增内部私有数据,通过「属性绑定」给插槽传递数据(:数据名="子组件内部数据"):
vue
xml
<template>
<div class="goods-card" style="width: 280px; border: 1px solid #f0f0f0; border-radius: 12px; padding: 16px; margin: 16px; float: left; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
<!-- 作用域插槽:商品图片(暴露商品id和图片地址) -->
<div class="goods-img" style="width: 100%; height: 180px; margin-bottom: 12px; text-align: center;">
<slot name="image" :goodsId="goods.id" :imgUrl="goods.imgUrl" />
</div>
<!-- 作用域插槽:商品标题(暴露商品名称和价格) -->
<div class="goods-title" style="font-size: 16px; font-weight: 500; margin-bottom: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<slot name="title" :goodsName="goods.name" :goodsPrice="goods.price" />
</div>
<!-- 作用域插槽:商品价格(暴露价格和优惠信息) -->
<div class="goods-price" style="font-size: 18px; color: #ff4400; margin-bottom: 16px;">
<slot name="price" :price="goods.price" :discount="goods.discount" />
</div>
<!-- 具名插槽:操作按钮(带默认内容) -->
<div class="goods-actions" style="text-align: center;">
<slot name="action">
<!-- 默认内容:加入购物车按钮 -->
<button style="width: 100%; padding: 8px 0; background: #ff4400; color: #fff; border: none; border-radius: 8px; cursor: pointer;">
加入购物车
</button>
</slot>
</div>
</div>
</template>
<script setup>
// 子组件内部私有数据(模拟接口返回,父组件无法直接访问)
const goods = {
id: 1001,
name: "小米手机 14 旗舰智能手机",
price: 4999,
imgUrl: "https://picsum.photos/240/180?random=1",
discount: "立减200元,支持分期免息"
};
</script>
2. 父组件:接收并使用作用域插槽数据(App.vue)
父组件通过 template #插槽名="插槽数据对象" 接收子组件暴露的数据,支持解构赋值简化代码,自定义渲染逻辑:
vue
xml
<template>
<h2>进阶:作用域插槽演示(子组件供数,父组件定制渲染)</h2>
<div style="overflow: hidden; clear: both; margin-top: 40px;">
<GoodsCard>
<!-- 接收图片插槽的作用域数据:slotProps(自定义名称,包含goodsId、imgUrl) -->
<template #image="slotProps">
<img :src="slotProps.imgUrl" :alt="'商品' + slotProps.goodsId" style="width: 240px; height: 180px; object-fit: cover; border-radius: 8px;">
<!-- 利用子组件传递的goodsId,添加自定义标识 -->
<span style="position: absolute; top: 8px; left: 8px; background: red; color: #fff; padding: 2px 8px; border-radius: 4px; z-index: 10;">
编号:{{ slotProps.goodsId }}
</span>
</template>
<!-- 接收标题插槽的作用域数据:解构赋值(更简洁,推荐) -->
<template #title="{ goodsName, goodsPrice }">
{{ goodsName }}
<!-- 父组件自定义逻辑:价格高于4000加「高端」标识 -->
<span v-if="goodsPrice > 4000" style="color: #ff4400; font-size: 12px; margin-left: 8px;">
高端
</span>
</template>
<!-- 接收价格插槽的作用域数据:结合优惠信息渲染 -->
<template #price="{ price, discount }">
<span>¥ {{ price }}</span>
<!-- 渲染子组件传递的优惠信息,自定义样式 -->
<p style="font-size: 12px; color: #999; margin-top: 4px; text-align: left;">
{{ discount }}
</p>
</template>
</GoodsCard>
</div>
</template>
<script setup>
import GoodsCard from './components/GoodsCard.vue';
</script>
3. 运行效果与说明
- 父组件成功获取子组件私有数据(
goodsId、discount等),并实现自定义渲染(商品编号、高端标识、优惠信息); - 核心语法:子组件「属性绑定传数据」,父组件「插槽数据对象接收」,支持解构赋值简化代码;
- 核心价值:通用组件(列表、卡片、表格)既保留内部数据逻辑,又开放渲染格式定制权,极大提升组件灵活性和复用性;
- 注意:作用域插槽本质仍是具名 / 默认插槽,只是增加了「子向父」的数据传递能力。
三、总结(核心知识点回顾,加深记忆)
- 使用步骤:
- 子组件:用
<slot name="xxx">定义具名插槽(内部可写默认内容),用:数据名="内部数据"给插槽传递数据(作用域插槽); - 父组件:用
<template #xxx>给指定具名插槽传内容,用<template #xxx="slotProps">接收作用域插槽数据,未指定插槽名的内容默认插入到<slot>(默认插槽)。
- 核心语法:
v-slot:插槽名可简写为#插槽名,仅能用于<template>标签或组件标签上;- 作用域插槽数据支持解构赋值,可设置默认值(如
#title="{ goodsName = '默认商品', goodsPrice = 0 }")避免报错。
- 插槽体系:
- 基础层:默认插槽(单一区域)、具名插槽(多区域精准定制);
- 增强层:插槽默认内容(提升健壮性)、作用域插槽(子供数 + 父定制,进阶核心)。