各位观众老爷,欢迎来到今天的 Vue 插槽专场!我是你们的老朋友,今天就来和大家唠唠 Vue 里那些既实用又有点小神秘的插槽。
开场白:插槽是啥玩意儿?
咱们先来聊聊,啥是插槽?想象一下,你买了一件衣服,衣服上预留了一些口子,你可以根据自己的喜好往里边塞东西,比如塞个口袋,塞个装饰品,甚至塞个小宠物(别真塞啊!)。在 Vue 里,插槽就相当于这些预留的口子,组件可以预留一些位置,让使用它的父组件往里边填充内容。
为啥要有插槽?因为组件要够灵活啊!如果不使用插槽,子组件的内容就固定死了,父组件只能干巴巴地用,毫无个性化可言。有了插槽,父组件就能根据自己的需求,往子组件里塞各种各样的内容,让子组件变得千变万化。
第一部分:插槽家族的成员介绍
插槽家族可热闹了,里面有默认插槽、具名插槽和作用域插槽这三位成员。咱们一个一个来认识。
1. 默认插槽(Default Slot):最朴素的那个
默认插槽,顾名思义,就是最简单、最常用的插槽。它就像一个没名字的口子,父组件往里塞东西的时候,不用指定塞哪个口子,直接一股脑塞进去就行了。
- 子组件的代码:
<!-- MyComponent.vue -->
<template>
<div>
<p>我是子组件的内容</p>
<slot></slot>
</div>
</template>
- 父组件的代码:
<!-- App.vue -->
<template>
<div>
<MyComponent>
<p>我是通过默认插槽传递的内容</p>
<button>点我啊!</button>
</MyComponent>
</div>
</template>
在这个例子里,<MyComponent>
组件中有一个 <slot>
标签,它就是默认插槽。父组件 <App.vue>
在使用 <MyComponent>
时,把 <p>
标签和 <button>
标签都塞进了这个默认插槽里。最终渲染出来的效果就是:
<div>
<p>我是子组件的内容</p>
<p>我是通过默认插槽传递的内容</p>
<button>点我啊!</button>
</div>
默认插槽的特点就是简单粗暴,啥也不用说,直接往里塞就行。但它也有缺点,就是只能有一个,如果你想在子组件里预留多个位置,让父组件分别填充不同的内容,那默认插槽就搞不定了。
2. 具名插槽(Named Slot):指名道姓的那个
为了解决默认插槽只能有一个的问题,Vue 引入了具名插槽。具名插槽就像给每个口子都贴上了标签,父组件往里塞东西的时候,必须指明要塞哪个口子。
- 子组件的代码:
<!-- MyComponent.vue -->
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
- 父组件的代码:
<!-- App.vue -->
<template>
<div>
<MyComponent>
<template v-slot:header>
<h1>我是头部</h1>
</template>
<template v-slot:default>
<p>我是主要内容</p>
</template>
<template v-slot:footer>
<p>我是页脚</p>
</template>
</MyComponent>
</div>
</template>
在这个例子里,<MyComponent>
组件定义了三个插槽:一个名为 "header" 的具名插槽,一个默认插槽,和一个名为 "footer" 的具名插槽。父组件 <App.vue>
在使用 <MyComponent>
时,使用 v-slot
指令来指定要往哪个插槽里塞内容。
v-slot
指令可以简写为 #
,所以上面的代码也可以写成这样:
<!-- App.vue -->
<template>
<div>
<MyComponent>
<template #header>
<h1>我是头部</h1>
</template>
<template #default>
<p>我是主要内容</p>
</template>
<template #footer>
<p>我是页脚</p>
</template>
</MyComponent>
</div>
</template>
最终渲染出来的效果就是:
<div>
<header>
<h1>我是头部</h1>
</header>
<main>
<p>我是主要内容</p>
</main>
<footer>
<p>我是页脚</p>
</footer>
</div>
具名插槽的特点就是可以定义多个,并且父组件可以精确地控制要往哪个插槽里塞内容。
3. 作用域插槽(Scoped Slot):带数据的那个
具名插槽解决了多个插槽的问题,但是它还有一个问题,就是父组件只能往插槽里塞静态的内容,无法使用子组件的数据。为了解决这个问题,Vue 引入了作用域插槽。
作用域插槽就像一个函数,子组件可以把一些数据作为参数传递给这个函数,父组件在使用插槽的时候,就可以拿到这些数据,并根据这些数据来渲染内容。
- 子组件的代码:
<!-- MyComponent.vue -->
<template>
<div>
<slot :user="user"></slot>
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: '张三',
age: 18
}
}
}
}
</script>
- 父组件的代码:
<!-- App.vue -->
<template>
<div>
<MyComponent>
<template v-slot:default="slotProps">
<p>姓名:{{ slotProps.user.name }}</p>
<p>年龄:{{ slotProps.user.age }}</p>
</template>
</MyComponent>
</div>
</template>
在这个例子里,<MyComponent>
组件在 <slot>
标签上绑定了一个 user
属性,这个属性的值就是子组件的数据。父组件 <App.vue>
在使用 <MyComponent>
时,使用 v-slot:default="slotProps"
来接收子组件传递的数据,并把这些数据赋值给 slotProps
变量。然后,父组件就可以使用 slotProps.user.name
和 slotProps.user.age
来访问子组件的数据了。
最终渲染出来的效果就是:
<div>
<p>姓名:张三</p>
<p>年龄:18</p>
</div>
作用域插槽的特点就是可以传递数据,父组件可以根据子组件的数据来渲染内容。
总结一下,插槽家族的成员特点:
插槽类型 | 特点 | 使用场景 |
---|---|---|
默认插槽 | 最简单,只能有一个,父组件往里塞内容,不用指定塞哪个口子。 | 子组件只需要一个内容填充位置,且不需要区分不同区域的内容。例如:一个简单的弹窗组件,只需要填充弹窗的主体内容。 |
具名插槽 | 可以有多个,父组件必须指明要往哪个口子塞内容。 | 子组件需要多个内容填充位置,且需要区分不同区域的内容。例如:一个页面布局组件,需要填充头部、主体、尾部等不同区域的内容。一个复杂的表格组件,需要填充表头、表体、表尾等不同区域的内容。 |
作用域插槽 | 可以传递数据,父组件可以根据子组件的数据来渲染内容。 | 子组件需要将一些内部数据暴露给父组件,让父组件根据这些数据来渲染内容。例如:一个列表组件,需要将列表中的每一项的数据传递给父组件,让父组件自定义每一项的渲染方式。一个下拉菜单组件,需要将选中的选项的数据传递给父组件,让父组件显示选中的选项。 |
第二部分:插槽的高级用法
了解了插槽的基本类型,咱们再来看看插槽的一些高级用法,让你的组件更加强大。
1. 默认内容(Fallback Content):备胎的自我修养
有时候,我们希望在父组件没有提供插槽内容的时候,子组件能够显示一些默认的内容。这就像一个备胎,在没有人需要它的时候,它就默默地在那里,一旦有人需要它,它就能挺身而出。
- 子组件的代码:
<!-- MyComponent.vue -->
<template>
<div>
<slot>
<p>我是默认内容</p>
</slot>
</div>
</template>
- 父组件的代码:
<!-- App.vue -->
<template>
<div>
<MyComponent></MyComponent>
<MyComponent>
<p>我是通过插槽传递的内容</p>
</MyComponent>
</div>
</template>
在这个例子里,<MyComponent>
组件的 <slot>
标签里有一个 <p>
标签,它就是默认内容。当父组件没有提供插槽内容的时候,子组件就会显示这个默认内容。当父组件提供了插槽内容的时候,子组件就会显示父组件的内容。
最终渲染出来的效果就是:
<div>
<p>我是默认内容</p>
</div>
<div>
<p>我是通过插槽传递的内容</p>
</div>
默认内容的作用就是让组件更加健壮,即使父组件没有提供插槽内容,组件也能正常显示。
2. 动态插槽名(Dynamic Slot Names):灵活多变的那个
有时候,我们希望根据不同的条件来选择不同的插槽。这就像一个百变怪,可以根据不同的情况变成不同的样子。
- 子组件的代码:
<!-- MyComponent.vue -->
<template>
<div>
<slot :name="slotName"></slot>
</div>
</template>
<script>
export default {
props: {
slotName: {
type: String,
default: 'default'
}
}
}
</script>
- 父组件的代码:
<!-- App.vue -->
<template>
<div>
<MyComponent :slotName="currentSlot">
<template #header>
<h1>我是头部</h1>
</template>
<template #default>
<p>我是主要内容</p>
</template>
<template #footer>
<p>我是页脚</p>
</template>
</MyComponent>
<button @click="toggleSlot">切换插槽</button>
</div>
</template>
<script>
export default {
data() {
return {
currentSlot: 'default'
}
},
methods: {
toggleSlot() {
this.currentSlot = this.currentSlot === 'default' ? 'header' : 'default';
}
}
}
</script>
在这个例子里,<MyComponent>
组件的 <slot>
标签的 name
属性绑定了一个 slotName
属性,这个属性的值决定了要使用哪个插槽。父组件 <App.vue>
通过 currentSlot
数据来控制 slotName
的值,从而实现动态切换插槽的效果。
动态插槽名的作用就是让组件更加灵活,可以根据不同的条件来选择不同的插槽。
3. 具名作用域插槽(Named Scoped Slots):既要名字也要数据
前面我们学习了具名插槽和作用域插槽,那么能不能把它们结合起来呢?答案是肯定的!具名作用域插槽就是既有名字又有数据的插槽。
- 子组件的代码:
<!-- MyComponent.vue -->
<template>
<div>
<slot name="header" :title="title"></slot>
</div>
</template>
<script>
export default {
data() {
return {
title: '我是标题'
}
}
}
</script>
- 父组件的代码:
<!-- App.vue -->
<template>
<div>
<MyComponent>
<template #header="slotProps">
<h1>{{ slotProps.title }}</h1>
</template>
</MyComponent>
</div>
</template>
在这个例子里,<MyComponent>
组件的 <slot>
标签的 name
属性是 "header",并且绑定了一个 title
属性,这个属性的值就是子组件的数据。父组件 <App.vue>
在使用 <MyComponent>
时,使用 v-slot:header="slotProps"
来接收子组件传递的数据,并把这些数据赋值给 slotProps
变量。然后,父组件就可以使用 slotProps.title
来访问子组件的数据了。
具名作用域插槽的作用就是让组件既能区分不同的区域,又能传递数据。
4. 解构插槽 Prop(Destructuring Slot Props):优雅地取数据
在使用作用域插槽的时候,我们通常需要从 slotProps
对象中取出需要的数据。如果数据比较多,就需要写很多 slotProps.xxx
,这样比较麻烦。Vue 提供了结构插槽 Prop 的语法,可以让我们更优雅地取出数据。
- 子组件的代码:
<!-- MyComponent.vue -->
<template>
<div>
<slot :user="user" :settings="settings"></slot>
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: '张三',
age: 18
},
settings: {
theme: 'dark',
language: 'zh-CN'
}
}
}
}
</script>
- 父组件的代码:
<!-- App.vue -->
<template>
<div>
<MyComponent>
<template #default="{ user, settings }">
<p>姓名:{{ user.name }}</p>
<p>年龄:{{ user.age }}</p>
<p>主题:{{ settings.theme }}</p>
<p>语言:{{ settings.language }}</p>
</template>
</MyComponent>
</div>
</template>
在这个例子里,父组件 <App.vue>
在使用 <MyComponent>
时,使用 v-slot:default="{ user, settings }"
来接收子组件传递的数据,并把 user
和 settings
数据分别赋值给 user
和 settings
变量。然后,父组件就可以直接使用 user.name
和 settings.theme
来访问子组件的数据了。
解构插槽 Prop 的作用就是让代码更加简洁,提高开发效率。
第三部分:插槽的实际应用场景
说了这么多理论,咱们来看看插槽在实际开发中都有哪些应用场景。
1. 布局组件(Layout Component):搭积木的乐趣
布局组件是最常见的插槽应用场景之一。我们可以创建一个通用的布局组件,然后在不同的页面中使用它,只需要往不同的插槽里塞入不同的内容即可。
- 布局组件的代码:
<!-- Layout.vue -->
<template>
<div class="layout">
<header class="layout-header">
<slot name="header"></slot>
</header>
<main class="layout-content">
<slot></slot>
</main>
<footer class="layout-footer">
<slot name="footer"></slot>
</footer>
</div>
</template>
<style scoped>
.layout {
display: flex;
flex-direction: column;
height: 100vh;
}
.layout-header {
height: 60px;
background-color: #f0f0f0;
}
.layout-content {
flex: 1;
padding: 20px;
}
.layout-footer {
height: 40px;
background-color: #f0f0f0;
text-align: center;
}
</style>
- 页面组件的代码:
<!-- HomePage.vue -->
<template>
<Layout>
<template #header>
<h1>首页</h1>
</template>
<template #default>
<p>欢迎来到首页!</p>
</template>
<template #footer>
<p>版权所有 © 2023</p>
</template>
</Layout>
</template>
<script>
import Layout from './Layout.vue';
export default {
components: {
Layout
}
}
</script>
2. 表格组件(Table Component):数据的艺术
表格组件也是一个很好的插槽应用场景。我们可以创建一个通用的表格组件,然后在不同的页面中使用它,只需要往不同的插槽里塞入不同的列数据即可。
- 表格组件的代码:
<!-- Table.vue -->
<template>
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column.key">
{{ column.title }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in data" :key="row.id">
<td v-for="column in columns" :key="column.key">
<slot :name="column.key" :row="row">{{ row[column.key] }}</slot>
</td>
</tr>
</tbody>
</table>
</template>
<script>
export default {
props: {
columns: {
type: Array,
required: true
},
data: {
type: Array,
required: true
}
}
}
</script>
- 页面组件的代码:
<!-- UserList.vue -->
<template>
<Table :columns="columns" :data="users">
<template #name="{ row }">
<a :href="`/users/${row.id}`">{{ row.name }}</a>
</template>
<template #age="{ row }">
<span>{{ row.age }} 岁</span>
</template>
</Table>
</template>
<script>
import Table from './Table.vue';
export default {
components: {
Table
},
data() {
return {
columns: [
{ key: 'name', title: '姓名' },
{ key: 'age', title: '年龄' },
{ key: 'email', title: '邮箱' }
],
users: [
{ id: 1, name: '张三', age: 18, email: '[email protected]' },
{ id: 2, name: '李四', age: 20, email: '[email protected]' },
{ id: 3, name: '王五', age: 22, email: '[email protected]' }
]
}
}
}
</script>
3. 弹窗组件(Modal Component):优雅的提示
弹窗组件也是一个常见的插槽应用场景。我们可以创建一个通用的弹窗组件,然后在不同的页面中使用它,只需要往不同的插槽里塞入不同的内容即可。
- 弹窗组件的代码:
<!-- Modal.vue -->
<template>
<div class="modal" v-if="visible">
<div class="modal-content">
<div class="modal-header">
<slot name="header">
<h2>{{ title }}</h2>
</slot>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button @click="close">关闭</button>
</slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: '提示'
}
},
methods: {
close() {
this.$emit('update:visible', false);
}
}
}
</script>
<style scoped>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #fff;
border-radius: 5px;
width: 500px;
}
.modal-header {
padding: 10px;
border-bottom: 1px solid #ccc;
}
.modal-body {
padding: 20px;
}
.modal-footer {
padding: 10px;
border-top: 1px solid #ccc;
text-align: right;
}
</style>
- 页面组件的代码:
<!-- MyPage.vue -->
<template>
<div>
<button @click="showModal">显示弹窗</button>
<Modal :visible.sync="modalVisible" title="确认删除">
<p>确定要删除这条记录吗?</p>
<template #footer>
<button @click="cancelDelete">取消</button>
<button @click="confirmDelete">确认删除</button>
</template>
</Modal>
</div>
</template>
<script>
import Modal from './Modal.vue';
export default {
components: {
Modal
},
data() {
return {
modalVisible: false
}
},
methods: {
showModal() {
this.modalVisible = true;
},
cancelDelete() {
this.modalVisible = false;
},
confirmDelete() {
// 删除记录
this.modalVisible = false;
}
}
}
</script>
第四部分:总结与展望
今天我们一起学习了 Vue 中插槽的各种类型和用法,以及它们在实际开发中的应用场景。希望通过今天的学习,大家能够更加灵活地使用插槽,让你的组件更加强大。
插槽是 Vue 中非常重要的一个特性,掌握插槽的使用,可以让你更好地组织代码,提高代码的复用性,让你的 Vue 项目更加易于维护。
未来,Vue 可能会在插槽方面有更多的改进和创新,让我们一起期待吧!
好了,今天的插槽专场就到这里,感谢大家的观看!我们下期再见!