文章目录
- [Vue 插槽详解:默认插槽、具名插槽与作用域插槽](#Vue 插槽详解:默认插槽、具名插槽与作用域插槽)
-
- 什么是插槽
- 一、默认插槽
-
- [1. 子组件先占位](#1. 子组件先占位)
- [2. 父组件传入内容](#2. 父组件传入内容)
- [3. 一个容易混淆的点](#3. 一个容易混淆的点)
- 二、具名插槽
-
- [1. 子组件定义多个具名插槽](#1. 子组件定义多个具名插槽)
- [2. 父组件用 `template + #插槽名` 分发内容](#插槽名` 分发内容)
- [3. 默认插槽的具名写法](#3. 默认插槽的具名写法)
- 三、作用域插槽
-
- [1. 子组件把数据绑定到 slot 上](#1. 子组件把数据绑定到 slot 上)
- [2. 父组件用 `#插槽名="scope"` 接收](#插槽名="scope"` 接收)
- [3. 作用域插槽的典型应用](#3. 作用域插槽的典型应用)
- 四、动态插槽名(进阶)
- 五、常见坑
- 小结
Vue 插槽详解:默认插槽、具名插槽与作用域插槽
写组件时经常遇到这样的需求:组件的外壳和布局是固定的,但里面的内容得由使用者决定。用 props 传字符串能应付简单场景,可一旦要传 HTML 结构、图片甚至一整段交互逻辑,props 就不够用了。这时候该 插槽(slot)上场。
什么是插槽
插槽可以理解为组件里预留的"坑位":子组件负责挖坑(写 <slot>),父组件负责填坑(在组件标签内部写内容)。
它解决的核心问题是:让组件的结构可复用,同时让内容可定制。
一、默认插槽
最简单的插槽,一个组件只有一个坑,没有名字。
1. 子组件先占位
AAA.vue(折叠面板组件)里,原本写死了四句诗:
xml
<template>
<div class="container" v-show="visible">
<p>生命诚可贵,</p>
<p>爱情价更高。</p>
<p>若为自由故,</p>
<p>两者皆可抛。</p>
</div>
</template>
把写死的内容换成 <slot>,就变成了一个可复用的容器:
xml
<template>
<div class="container" v-show="visible">
<slot>默认值</slot>
</div>
</template>
<slot> 标签内的内容就是默认内容------当父组件没有传入任何内容时,显示它。
2. 父组件传入内容
App.vue 里使用 AAA 组件,并在标签内部填写内容:
xml
<template>
<h3>折叠面板</h3>
<!-- 展示一首诗 -->
<AAA>
<p>生命诚可贵,</p>
<p>爱情价更高。</p>
<p>若为自由故,</p>
<p>两者皆可抛。</p>
</AAA>
<!-- 展示 2 张图片 -->
<AAA>
<div class="img-box">
<img width="100%" src="./assets/lq.png" alt="lq" />
<img width="100%" src="./assets/sy.png" alt="sy" />
</div>
</AAA>
</template>
同一个 AAA 组件,第一次装了一首诗,第二次装了两张图。组件结构没变,内容完全由调用方决定。
3. 一个容易混淆的点
xml
<!-- 这种写法是父给子传参数(props) -->
<AAA title="标题" :list="data" />
<!-- 这种写法是插槽,用来替换 <slot></slot> -->
<AAA>
<p>这里是插槽内容</p>
</AAA>
区别很直观:写在标签属性里的是 props,写在标签内部的是插槽内容。
二、具名插槽
当一个组件需要多个坑位时(比如头部、主体、底部),就得给每个 <slot> 起个名字。
1. 子组件定义多个具名插槽
xml
<!-- Card.vue -->
<template>
<div class="card">
<header>
<slot name="header">默认标题</slot>
</header>
<main>
<!-- 没有 name 的就是默认插槽 -->
<slot>默认内容</slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
要点:
name="header"这种就是具名插槽- 不带
name的<slot>等价于name="default",即默认插槽 - 每个插槽都可以有自己的默认内容
2. 父组件用 template + #插槽名 分发内容
xml
<!-- App.vue -->
<template>
<Card>
<template #header>
<h2>用户信息</h2>
</template>
<!-- 没有指定名字的内容会进入默认插槽 -->
<p>这是主体内容</p>
<template #footer>
<button>确定</button>
<button>取消</button>
</template>
</Card>
</template>
#header 是 v-slot:header 的简写,完整写法是:
xml
<template v-slot:header>...</template>
<!-- 等价于 -->
<template #header>...</template>
3. 默认插槽的具名写法
默认插槽也可以显式写出来:
xml
<Card>
<template #default>
<p>主体内容</p>
</template>
</Card>
注意是 #default,不是单独一个 #。
三、作用域插槽
前面两种插槽都是父组件往子组件里塞内容,数据流向是"父 → 子"。但有时需求反过来:子组件手里有数据,想让父组件来决定怎么渲染。
这就是作用域插槽 :子组件通过 <slot> 把数据"暴露"出去,父组件接收后自行决定展示方式。
1. 子组件把数据绑定到 slot 上
xml
<!-- List.vue -->
<script setup>
defineProps({
list: {
type: Array,
default: () => []
}
})
</script>
<template>
<ul class="list">
<li v-for="(item, index) in list" :key="index">
<!-- 把 item 和 index 传给父组件 -->
<slot :item="item" :index="index">
{{ item.name }}
</slot>
</li>
</ul>
</template>
:item="item" 和 :index="index" 就是暴露给父组件的数据。如果父组件不传内容,就走默认显示 {``{ item.name }}。
2. 父组件用 #插槽名="scope" 接收
xml
<!-- App.vue -->
<script setup>
import { reactive } from 'vue'
const users = reactive([
{ name: '张三', age: 20, city: '北京' },
{ name: '李四', age: 25, city: '上海' }
])
</script>
<template>
<List :list="users">
<template #default="{ item, index }">
<span class="row">
{{ index + 1 }}. {{ item.name }}({{ item.age }} 岁,来自{{ item.city }})
</span>
</template>
</List>
</template>
几点说明:
#default="scope"中的scope是一个对象,包含子组件传过来的所有属性,这里是{ item, index }- 直接解构
{ item, index }更简洁,推荐这种写法 - 换一个父组件场景,同一个
List可以渲染成表格、卡片、树形结构,渲染逻辑完全由父组件决定
3. 作用域插槽的典型应用
- 表格组件:列的内容由使用者自定义
- 下拉选择组件:选项的显示样式由使用者决定
- 列表组件:每一项的渲染方式由使用者控制
- 图表库封装:数据项如何展示由调用方定义
四、动态插槽名(进阶)
插槽名也可以是变量:
xml
<template>
<Card>
<template #[dynamicSlotName]>
<p>动态内容</p>
</template>
</Card>
</template>
<script setup>
import { ref } from 'vue'
const dynamicSlotName = ref('header')
</script>
适合在配置化组件里根据配置项决定往哪个插槽塞内容。
五、常见坑
v-slot只能用在<template>或组件标签上 :不能写在普通 HTML 元素(如div)上,否则无效。- 简写
#后面不能有空格 :#header合法,# header会报错。 - 插槽内容的编译作用域在父组件 :父组件模板里的所有内容都在父组件作用域编译,因此只能访问父组件的数据,不能直接访问子组件的数据。要拿子组件的数据,必须用作用域插槽。
- 默认插槽的简写是
#default:不是单独一个#。 - 多个默认插槽会警告:一个组件里只能有一个默认插槽,其余必须具名。
- 插槽内容是响应式的:父组件的数据变化后,插槽内容也会跟着更新,不用额外处理。
小结
三种插槽,一句话记住:
| 类型 | 特点 | 典型场景 |
|---|---|---|
| 默认插槽 | 一个坑,谁用谁填 | 通用容器、折叠面板 |
| 具名插槽 | 多个坑,按名字对号入座 | 卡片、对话框、布局组件 |
| 作用域插槽 | 子组件挖坑并提供数据,父组件决定怎么展示 | 列表、表格、下拉框 |