详解Vue中的插槽
作者: 小土豆
博客园:https://www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/2436173500265335
什么是插槽
在日常的项目开发中,当我们在编写一个完整的组件时,不可避免的会引用一些外部组件
或者自定义组件
。
有了这种引用关系
之后,我们就可以把它们称为父组件
或者子组件
,同时父子组件
之间有很多的通信方式,比如可以通过props
向子组件
传递数据,或者通过$emit
、$parent
调用父组件
中的方法。
下面就是一个非常简单的父组件
引用子组件
的例子。
<!-- 子组件: /slot-demo/src/components/Child.vue -->
<template>
<div class="child">
<!-- 标记 -->
<div>
<i class="el-icon-s-flag"></i>Badge 标记
<div class="content">
<el-badge :value="12" class="item">
<el-button size="small">评论</el-button>
</el-badge>
</div>
</div>
<!-- 进度条 -->
<div>
<i class="el-icon-s-flag"></i>进度条
<div class="content">
<el-progress :percentage="50"></el-progress>
</div>
</div>
</div>
</template>
<!-- 省略其它代码 -->
接着我们在App
组件中引用Child
组件。
<!-- 父组件: /slot-demo/src/App.vue -->
<template>
<div id="app">
<!-- 使用子组件 -->
<child></child>
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
}
}
</script>
最后运行项目,子组件
的内容成功被引用并展示在页面上。
那假如我们现在有这样一个需求:在引用Child
组件的同时,希望在Child
组件的指定位置
插入一段内容:<h1> 欢迎大家关注小土豆 </h1>
。
如果我们直接将内容写入<child></child>
内部,是不会生效的。
<!-- 父组件: /slot-demo/src/App.vue -->
<template>
<div id="app">
<!-- 使用子组件 -->
<child>
<h1> 欢迎大家关注小土豆 </h1>
</child>
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
}
}
</script>
可以看到并未达到预期效果:
那为了解决类似这样的问题,Vue
就设计出来了slot
这个东西。slot
翻译过来叫做插槽
,也可称其为Vue
的内容分发机制,它的主要作用就是向子组件
的指定位置
插入一段内容,这个内容可以是HTML
或者其他的组件
。
默认插槽
在前面一节内容里,我们提出了一个需求:在引用Child
组件的同时,希望在Child
组件的指定位置
插入一段内容:<h1> 欢迎大家关注小土豆 </h1>
。
那这个需求如何使用插槽
来实现呢?我们来实践一下。
首先我们需要在子组件
中写入<slot></slot>
,同时这个在<slot>
标签内部可以有默认的内容,比如<slot>我是这个slot里面本来的内容</slot>
<!-- 子组件: /src/components/Child.vue -->
<template>
<div class="child">
<!-- 标记 -->
<div>
<i class="el-icon-s-flag"></i>Badge 标记
<div class="content">
<el-badge :value="12" class="item">
<el-button size="small">评论</el-button>
</el-badge>
</div>
</div>
<!-- 进度条 -->
<div>
<i class="el-icon-s-flag"></i>进度条
<div class="content">
<el-progress :percentage="50"></el-progress>
</div>
</div>
<!-- 占位符 -->
<slot>我是这个slot里面本来的内容</slot>
</div>
</template>
<!-- 省略其它代码 -->
接着就是在父组件
中传入我们希望插入到子组件
中的内容。
<!-- 父组件: /src/App.vue -->
<template>
<div id="app">
<!-- 使用子组件 -->
<child>
<h1> 欢迎大家关注小土豆 </h1>
</child>
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
}
}
</script>
此时在运行项目,就能看到<h1> 欢迎大家关注小土豆 </h1>
这段内容已经成功的显示在页面上。
具名插槽
具名插槽
就是给我们的插槽
起一个名字,即给<slot></slot>
定义一个name
属性。
<!-- 插槽名称为:heading -->
<slot name="heading"></slot>
<!-- 插槽名称为:sub-heading -->
<slot name="sub-heading"></slot>
<!-- 插槽名称为:footer-text -->
<slot name="footer-text"></slot>
给插槽
起了名称以后,我们在父组件
中就可以使用v-slot:name
或者#name
往指定的插槽
填充内容。
#name
是v-slot:name
的简写形式
下面我们就来实践一下具名插槽
。
首先是在子组件(Child.vue)
中定义具名插槽
。
<!-- 子组件: /slot-demo/src/components/Child.vue -->
<template>
<div class="child">
<!-- 插槽名称为:heading -->
<slot name="heading"></slot>
<!-- 插槽名称为:sub-heading -->
<slot name="sub-heading"></slot>
<!-- 标记 -->
<div>
<i class="el-icon-s-flag"></i>Badge 标记
<div class="content">
<el-badge :value="12" class="item">
<el-button size="small">评论</el-button>
</el-badge>
</div>
</div>
<!-- 进度条 -->
<div>
<i class="el-icon-s-flag"></i>进度条
<div class="content">
<el-progress :percentage="50"></el-progress>
</div>
</div>
<!-- 插槽名称为:footer-text -->
<slot name="footer-text"></slot>
</div>
</template>
<!-- 省略其它代码 -->
接着在父组件(App.vue)
中使用。
<!-- 父组件: /slot-demo/src/App.vue -->
<template>
<div id="app">
<!-- 使用子组件 -->
<child>
<template v-slot:heading>
<h1>element-ui组件</h1>
</template>
<template v-slot:sub-heading>
<p>这里是element-ui的部分组件介绍</p>
</template>
<template v-slot:footer-text>
<p>出品@小土豆</p>
</template>
</child>
</div>
</template>
<script>
import Child from './components/Child.vue'
export default {
name: 'App',
components: {
Child
}
}
</script>
运行项目就能看到对应的内容被插入到对应的插槽内:
补充内容——默认插槽的name
属性
其实关于前面的默认插槽
它也是有name
属性的,其值为default
,所以在父组件
中也可以这样写:
<!-- 父组件: /slot-demo/src/App.vue -->
<child>
<template v-slot:defalut>
<h1> 欢迎大家关注小土豆 </h1>
</template>
</child>
补充内容——<template>
元素上使用 v-slot
指令
在演示具名插槽
的时候,我们的v-slot
是写在<template>
元素上的,这个是比较推荐的写法,因为<template>
在处理的过程中不会渲染成真实的DOM
节点。
<template v-slot="default">
<h1>欢迎关注小土豆</h1>
</template>
处理之后的DOM
节点:
<h1 data-v-2dcc19c8="">欢迎关注小土豆</h1>
当然我们也可以将v-slot
应用在其他的HTML
元素上,这样最终插入到子组件中的内容就会有一层真实的DOM
节点包裹。
<div class="text" v-slot="default">
<h1>欢迎关注小土豆</h1>
</div>
处理之后的DOM
节点:
<div data-v-2dcc19c8="" class="text">
<h1 data-v-2dcc19c8="">欢迎关注小土豆</h1>
</div>
作用域插槽
关于作用域插槽
的相关概念和示例看了很多,但相对于前面两种类型的插槽来说,确实有些难以理解。如果需要用一句话去总结作用域插槽
,那就是在父组件中访问子组件的数据
,或者从数据流向
的角度来讲就是将子组件的数据传递到父组件
。
一个新概念或者一个新技术的出现总是有原因的,那作用域插槽
的出现又是为了解决什么样的问题呢?一起来研究一下吧。
作用域插槽的使用
我们先来看看如何利用作用域插槽
实现在父组件中访问子组件的数据
。
首先我们需要在子组件
的插槽<slot><slot>
上使用v-bind
绑定对应的数据。
<!-- 子组件: /slot-demo/src/components/Child.vue -->
<template>
<div class="child">
<slot
name="heading"
v-bind:headingValue="heading">
{{heading}}
</slot>
<!-- 为了让大家看的更清楚 已经将Child.vue组件中多余的内容删除 -->
</div>
</template>
<script>
export default {
name: 'Child',
data() {
return {
heading: '这里是默认的heading'
}
}
}
</script>
可以看到我们在<slot>
上使用v-bind
绑定了vue data
中定义的heading
数据。
接着我们就可以在父组件
中定义一个变量
来接收子组件
中传递的数据。
父组件中接收数据的
变量名
可以随意起,这里我起的变量名为slotValue
<!-- 父组件: /slot-demo/src/App.vue -->
<template>
<div id="app">
<child>
<template v-slot:heading="slotValue" >
<h1>element-ui组件</h1>
slotValue = {{slotValue}}
</template>
</child>
</div>
</template>
运行项目后查看页面的结果:
可以看到slotValue
是一个对象,保存了一组数据,其键
就是我们在子组件
的<slot>
上使用v-bind
绑定的属性名headingValue
,其值
是v-bind
绑定的heading
值。
作用域插槽的应用场景
前面我们了解了作用域插槽
的用法,也得知其主要目的是为了能在父组件
中访问子组件
的数据。那什么时候父组件
需要访问子组件
的数据呢。
我们来举个简单的栗子。
假设我们有下面这样一个Card
组件:
<!-- Card组件:/slot-demo/src/components/Card.vue -->
<template>
<div class="card">
<h3>{{title}}</h3>
<p v-for="item in list" :key="item.id">
{{item.id}}.{{item.text}}
</p>
</div>
</template>
<script>
export default {
name: 'Card',
props: ['title', 'list'],
data() {
return {
}
}
}
</script>
<style scoped>
.list{
border: 1px solid;
padding: 20px;
}
.list p{
border-bottom: 2px solid #fff;
padding-bottom: 5px;
}
</style>
其中Card
组件中展示的title
和list
数据由父组件
传入。
接着在App
组件中复用Card
组件,并且传入title
和list
数据。
<!-- App组件:/slot-demo/src/App.vue -->
<template>
<div id="app">
<card :list="list" :title="title">
</card>
</div>
</template>
<script>
import Card from './components/Card.vue'
export default {
name: 'App',
components: {
Card,
},
data() {
return {
title: '名人名言',
list:[
{
id:1,
text:'要成功,先发疯,头脑简单向前冲'
},{
id:2,
text:'不能天生丽质就只能天生励志!'
},{
id:3,
text:'世上唯一不能复制的是时间,唯一不能重演的是人生。'
}
]
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* text-align: center; */
/* color: #2c3e50; */
/* margin-top: 60px; */
color: #fff;
background: rgba(232, 0, 0, 0.3);
padding: 20px;
}
</style>
运行项目查看页面:
Card
组件本身并不复杂,就是展示title
和list
里面的数据。但凡是有相同需求的都可以通过复用Card
组件来实现。
但是仔细去想,我们的Card
组件其实并没有那么灵活:如果有些页面需要复用
该组件,但是希望在title
处增加一个图标
;或者有些页面需要在展示内容时候不显示编号1、2、3
。
那这样的需求使用插槽
就可以轻松实现。
<!-- Card组件:/slot-demo/src/components/Card.vue -->
<template>
<div class="card">
<h3>
<slot name="title" v-bind:titleValue="title"> {{title}} </slot>
</h3>
<p v-for="item in list" :key="item.id">
<slot name="text" v-bind:itemValue="item">{{item.id}}.{{item.text}}</slot>
</p>
</div>
</template>
我们在Card
组件展示title
和list
的位置分别添加了对应的具名插槽
,并且通过v-bind
将title
、item
(list
循环出来的数据)传递给了父组件
。
此时父组件
就可以控制子组件
的显示。
假如我们需要在title
处添加图标,则App
组件复用Card
组件的方式如下:
<!-- App组件:/slot-demo/src/App.vue -->
<card :list="list" :title="title">
<template v-slot:title="slotTitle">
<i class="el-icon-guide"></i>{{slotTitle.titleValue}}
</template>
</card>
页面效果:
亦或者有些页面需要在展示内容时候不显示编号1、2、3
:
<!-- App组件:/slot-demo/src/App.vue -->
<card :list="list" :title="title">
<template v-slot:text="slotItem">
{{slotItem.itemValue.text}}
</template>
</card>
页面效果:
这里应该能想起来
element table
组件的实现方式,是不是也有点这样的意思呢
到这里或许有人会说这样的需求不用插槽也能实现,直接在Card
组件中增加一些逻辑即可。这样的说法固然是可以实现功能,但是显然不是一个好办法。
因为组件的设计本身是希望拿来复用的,如果这个组件本身大部分实现是符合我们的需求的,只有一小部分不符合,我们首先应该想要的是去扩展该组件
,而不是修改组件
,这也是软件设计的思想:开放扩展,关闭修改
。所以插槽
的出现正是对组件的一种扩展,让我们可以更加灵活的复用组件。
废弃的插槽语法
关于以上所描述的插槽
语法,均是vue 2.6.0
以后的语法。在这之前,插槽的语法为slot(默认插槽或者具名插槽)
和slot-scope(作用域插槽)
。
默认插槽
<!-- Card组件:/slot-demo/src/components/Card.vue -->
<!-- 子组件的写法依然不变 -->
<slot></slot>
<!-- App组件:/slot-demo/src/App.vue -->
<child>
<template>
<h1>欢迎关注小土豆</h1>
</template>
<child>
<p>或者</p>
<child>
<template slot="default">
<h1>欢迎关注小土豆</h1>
</template>
<child>
页面效果:
具名插槽
<!-- Card组件:/slot-demo/src/components/Card.vue -->
<!-- 子组件的写法依然不变 -->
<!-- 插槽名称为:heading -->
<slot name="heading"></slot>
<!-- 插槽名称为:sub-heading -->
<slot name="sub-heading"></slot>
<!-- 插槽名称为:footer-text -->
<slot name="footer-text"></slot>
<!-- App组件:/slot-demo/src/App.vue -->
<child>
<template slot="heading">
<h1>element-ui组件</h1>
</template>
<template slot="sub-heading">
<p>这里是element-ui的部分组件介绍</p>
</template>
<template slot="footer-text">
<p>出品@小土豆</p>
</template>
</child>
页面效果:
作用域插槽
<!-- 子组件: /src/components/Child.vue -->
<slot
name="heading"
v-bind:headingValue="heading">
{{heading}}
</slot>
<!-- 父组件: /slot-demo/src/App.vue -->
<child>
<template slot="heading" slot-scope="headingValue" >
<h1>element-ui组件</h1>
headingValue = {{headingValue}}
</template>
</child>
页面效果:
总结
到这里本篇文章就结束了,内容非常简单易懂,可以是茶余饭后的一篇知识回顾。
最后我们在来做一个小小的总结:
近期文章
骨架屏(page-skeleton-webpack-plugin)初探
Vue结合Django-Rest-Frameword实现登录认证(二)
Vue结合Django-Rest-Frameword实现登录认证(一)
写在最后
如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者
文章公众号
首发,关注 不知名宝藏程序媛
第一时间获取最新的文章
笔芯❤️~