作者: 小土豆

博客园:https://www.cnblogs.com/HouJiao/

掘金:https://juejin.im/user/2436173500265335

什么是插槽

在日常的项目开发中,当我们在编写一个完整的组件时,不可避免的会引用一些外部组件或者自定义组件

有了这种引用关系之后,我们就可以把它们称为父组件或者子组件,同时父子组件之间有很多的通信方式,比如可以通过props子组件传递数据,或者通过$emit$parent调用父组件中的方法。

下面就是一个非常简单的父组件引用子组件的例子。

  1. <!-- 子组件: /slot-demo/src/components/Child.vue -->
  2. <template>
  3. <div class="child">
  4. <!-- 标记 -->
  5. <div>
  6. <i class="el-icon-s-flag"></i>Badge 标记
  7. <div class="content">
  8. <el-badge :value="12" class="item">
  9. <el-button size="small">评论</el-button>
  10. </el-badge>
  11. </div>
  12. </div>
  13. <!-- 进度条 -->
  14. <div>
  15. <i class="el-icon-s-flag"></i>进度条
  16. <div class="content">
  17. <el-progress :percentage="50"></el-progress>
  18. </div>
  19. </div>
  20. </div>
  21. </template>
  22. <!-- 省略其它代码 -->

接着我们在App组件中引用Child组件。

  1. <!-- 父组件: /slot-demo/src/App.vue -->
  2. <template>
  3. <div id="app">
  4. <!-- 使用子组件 -->
  5. <child></child>
  6. </div>
  7. </template>
  8. <script>
  9. import Child from './components/Child.vue'
  10. export default {
  11. name: 'App',
  12. components: {
  13. Child
  14. }
  15. }
  16. </script>

最后运行项目,子组件的内容成功被引用并展示在页面上。

那假如我们现在有这样一个需求:在引用Child组件的同时,希望在Child组件的指定位置插入一段内容:<h1> 欢迎大家关注小土豆 </h1>

如果我们直接将内容写入<child></child>内部,是不会生效的。

  1. <!-- 父组件: /slot-demo/src/App.vue -->
  2. <template>
  3. <div id="app">
  4. <!-- 使用子组件 -->
  5. <child>
  6. <h1> 欢迎大家关注小土豆 </h1>
  7. </child>
  8. </div>
  9. </template>
  10. <script>
  11. import Child from './components/Child.vue'
  12. export default {
  13. name: 'App',
  14. components: {
  15. Child
  16. }
  17. }
  18. </script>

可以看到并未达到预期效果:

那为了解决类似这样的问题,Vue就设计出来了slot这个东西。slot翻译过来叫做插槽,也可称其为Vue的内容分发机制,它的主要作用就是向子组件指定位置插入一段内容,这个内容可以是HTML或者其他的组件

默认插槽

在前面一节内容里,我们提出了一个需求:在引用Child组件的同时,希望在Child组件的指定位置插入一段内容:<h1> 欢迎大家关注小土豆 </h1>

那这个需求如何使用插槽来实现呢?我们来实践一下。

首先我们需要在子组件中写入<slot></slot>,同时这个在<slot>标签内部可以有默认的内容,比如<slot>我是这个slot里面本来的内容</slot>

  1. <!-- 子组件: /src/components/Child.vue -->
  2. <template>
  3. <div class="child">
  4. <!-- 标记 -->
  5. <div>
  6. <i class="el-icon-s-flag"></i>Badge 标记
  7. <div class="content">
  8. <el-badge :value="12" class="item">
  9. <el-button size="small">评论</el-button>
  10. </el-badge>
  11. </div>
  12. </div>
  13. <!-- 进度条 -->
  14. <div>
  15. <i class="el-icon-s-flag"></i>进度条
  16. <div class="content">
  17. <el-progress :percentage="50"></el-progress>
  18. </div>
  19. </div>
  20. <!-- 占位符 -->
  21. <slot>我是这个slot里面本来的内容</slot>
  22. </div>
  23. </template>
  24. <!-- 省略其它代码 -->

接着就是在父组件中传入我们希望插入到子组件中的内容。

  1. <!-- 父组件: /src/App.vue -->
  2. <template>
  3. <div id="app">
  4. <!-- 使用子组件 -->
  5. <child>
  6. <h1> 欢迎大家关注小土豆 </h1>
  7. </child>
  8. </div>
  9. </template>
  10. <script>
  11. import Child from './components/Child.vue'
  12. export default {
  13. name: 'App',
  14. components: {
  15. Child
  16. }
  17. }
  18. </script>

此时在运行项目,就能看到<h1> 欢迎大家关注小土豆 </h1>这段内容已经成功的显示在页面上。

具名插槽

具名插槽就是给我们的插槽起一个名字,即给<slot></slot>定义一个name属性。

  1. <!-- 插槽名称为:heading -->
  2. <slot name="heading"></slot>
  3. <!-- 插槽名称为:sub-heading -->
  4. <slot name="sub-heading"></slot>
  5. <!-- 插槽名称为:footer-text -->
  6. <slot name="footer-text"></slot>

插槽起了名称以后,我们在父组件中就可以使用v-slot:name或者#name往指定的插槽填充内容。

#namev-slot:name的简写形式

下面我们就来实践一下具名插槽

首先是在子组件(Child.vue)中定义具名插槽

  1. <!-- 子组件: /slot-demo/src/components/Child.vue -->
  2. <template>
  3. <div class="child">
  4. <!-- 插槽名称为:heading -->
  5. <slot name="heading"></slot>
  6. <!-- 插槽名称为:sub-heading -->
  7. <slot name="sub-heading"></slot>
  8. <!-- 标记 -->
  9. <div>
  10. <i class="el-icon-s-flag"></i>Badge 标记
  11. <div class="content">
  12. <el-badge :value="12" class="item">
  13. <el-button size="small">评论</el-button>
  14. </el-badge>
  15. </div>
  16. </div>
  17. <!-- 进度条 -->
  18. <div>
  19. <i class="el-icon-s-flag"></i>进度条
  20. <div class="content">
  21. <el-progress :percentage="50"></el-progress>
  22. </div>
  23. </div>
  24. <!-- 插槽名称为:footer-text -->
  25. <slot name="footer-text"></slot>
  26. </div>
  27. </template>
  28. <!-- 省略其它代码 -->

接着在父组件(App.vue)中使用。

  1. <!-- 父组件: /slot-demo/src/App.vue -->
  2. <template>
  3. <div id="app">
  4. <!-- 使用子组件 -->
  5. <child>
  6. <template v-slot:heading>
  7. <h1>element-ui组件</h1>
  8. </template>
  9. <template v-slot:sub-heading>
  10. <p>这里是element-ui的部分组件介绍</p>
  11. </template>
  12. <template v-slot:footer-text>
  13. <p>出品@小土豆</p>
  14. </template>
  15. </child>
  16. </div>
  17. </template>
  18. <script>
  19. import Child from './components/Child.vue'
  20. export default {
  21. name: 'App',
  22. components: {
  23. Child
  24. }
  25. }
  26. </script>

运行项目就能看到对应的内容被插入到对应的插槽内:

补充内容——默认插槽的name属性

其实关于前面的默认插槽它也是有name属性的,其值为default,所以在父组件中也可以这样写:

  1. <!-- 父组件: /slot-demo/src/App.vue -->
  2. <child>
  3. <template v-slot:defalut>
  4. <h1> 欢迎大家关注小土豆 </h1>
  5. </template>
  6. </child>

补充内容——<template> 元素上使用 v-slot 指令

在演示具名插槽的时候,我们的v-slot是写在<template>元素上的,这个是比较推荐的写法,因为<template>在处理的过程中不会渲染成真实的DOM节点。


  1. <template v-slot="default">
  2. <h1>欢迎关注小土豆</h1>
  3. </template>

处理之后的DOM节点:

  1. <h1 data-v-2dcc19c8="">欢迎关注小土豆</h1>

当然我们也可以将v-slot应用在其他的HTML元素上,这样最终插入到子组件中的内容就会有一层真实的DOM节点包裹。

  1. <div class="text" v-slot="default">
  2. <h1>欢迎关注小土豆</h1>
  3. </div>

处理之后的DOM节点:

  1. <div data-v-2dcc19c8="" class="text">
  2. <h1 data-v-2dcc19c8="">欢迎关注小土豆</h1>
  3. </div>

作用域插槽

关于作用域插槽的相关概念和示例看了很多,但相对于前面两种类型的插槽来说,确实有些难以理解。如果需要用一句话去总结作用域插槽,那就是在父组件中访问子组件的数据,或者从数据流向的角度来讲就是将子组件的数据传递到父组件

一个新概念或者一个新技术的出现总是有原因的,那作用域插槽的出现又是为了解决什么样的问题呢?一起来研究一下吧。

作用域插槽的使用

我们先来看看如何利用作用域插槽实现在父组件中访问子组件的数据

首先我们需要在子组件的插槽<slot><slot>上使用v-bind绑定对应的数据。

  1. <!-- 子组件: /slot-demo/src/components/Child.vue -->
  2. <template>
  3. <div class="child">
  4. <slot
  5. name="heading"
  6. v-bind:headingValue="heading">
  7. {{heading}}
  8. </slot>
  9. <!-- 为了让大家看的更清楚 已经将Child.vue组件中多余的内容删除 -->
  10. </div>
  11. </template>
  12. <script>
  13. export default {
  14. name: 'Child',
  15. data() {
  16. return {
  17. heading: '这里是默认的heading'
  18. }
  19. }
  20. }
  21. </script>

可以看到我们在<slot>上使用v-bind绑定了vue data中定义的heading数据。

接着我们就可以在父组件中定义一个变量来接收子组件中传递的数据。

父组件中接收数据的变量名可以随意起,这里我起的变量名为slotValue

  1. <!-- 父组件: /slot-demo/src/App.vue -->
  2. <template>
  3. <div id="app">
  4. <child>
  5. <template v-slot:heading="slotValue" >
  6. <h1>element-ui组件</h1>
  7. slotValue = {{slotValue}}
  8. </template>
  9. </child>
  10. </div>
  11. </template>

运行项目后查看页面的结果:

可以看到slotValue是一个对象,保存了一组数据,其就是我们在子组件<slot>上使用v-bind绑定的属性名headingValue,其v-bind绑定的heading值。

作用域插槽的应用场景

前面我们了解了作用域插槽的用法,也得知其主要目的是为了能在父组件中访问子组件的数据。那什么时候父组件需要访问子组件的数据呢。

我们来举个简单的栗子。

假设我们有下面这样一个Card组件:

  1. <!-- Card组件:/slot-demo/src/components/Card.vue -->
  2. <template>
  3. <div class="card">
  4. <h3>{{title}}</h3>
  5. <p v-for="item in list" :key="item.id">
  6. {{item.id}}.{{item.text}}
  7. </p>
  8. </div>
  9. </template>
  10. <script>
  11. export default {
  12. name: 'Card',
  13. props: ['title', 'list'],
  14. data() {
  15. return {
  16. }
  17. }
  18. }
  19. </script>
  20. <style scoped>
  21. .list{
  22. border: 1px solid;
  23. padding: 20px;
  24. }
  25. .list p{
  26. border-bottom: 2px solid #fff;
  27. padding-bottom: 5px;
  28. }
  29. </style>

其中Card组件中展示的titlelist数据由父组件传入。

接着在App组件中复用Card组件,并且传入titlelist数据。

  1. <!-- App组件:/slot-demo/src/App.vue -->
  2. <template>
  3. <div id="app">
  4. <card :list="list" :title="title">
  5. </card>
  6. </div>
  7. </template>
  8. <script>
  9. import Card from './components/Card.vue'
  10. export default {
  11. name: 'App',
  12. components: {
  13. Card,
  14. },
  15. data() {
  16. return {
  17. title: '名人名言',
  18. list:[
  19. {
  20. id:1,
  21. text:'要成功,先发疯,头脑简单向前冲'
  22. },{
  23. id:2,
  24. text:'不能天生丽质就只能天生励志!'
  25. },{
  26. id:3,
  27. text:'世上唯一不能复制的是时间,唯一不能重演的是人生。'
  28. }
  29. ]
  30. }
  31. }
  32. }
  33. </script>
  34. <style>
  35. #app {
  36. font-family: 'Avenir', Helvetica, Arial, sans-serif;
  37. -webkit-font-smoothing: antialiased;
  38. -moz-osx-font-smoothing: grayscale;
  39. /* text-align: center; */
  40. /* color: #2c3e50; */
  41. /* margin-top: 60px; */
  42. color: #fff;
  43. background: rgba(232, 0, 0, 0.3);
  44. padding: 20px;
  45. }
  46. </style>

运行项目查看页面:

Card组件本身并不复杂,就是展示titlelist里面的数据。但凡是有相同需求的都可以通过复用Card组件来实现。

但是仔细去想,我们的Card组件其实并没有那么灵活:如果有些页面需要复用该组件,但是希望在title处增加一个图标;或者有些页面需要在展示内容时候不显示编号1、2、3

那这样的需求使用插槽就可以轻松实现。

  1. <!-- Card组件:/slot-demo/src/components/Card.vue -->
  2. <template>
  3. <div class="card">
  4. <h3>
  5. <slot name="title" v-bind:titleValue="title"> {{title}} </slot>
  6. </h3>
  7. <p v-for="item in list" :key="item.id">
  8. <slot name="text" v-bind:itemValue="item">{{item.id}}.{{item.text}}</slot>
  9. </p>
  10. </div>
  11. </template>

我们在Card组件展示titlelist的位置分别添加了对应的具名插槽,并且通过v-bindtitleitemlist循环出来的数据)传递给了父组件

此时父组件就可以控制子组件的显示。

假如我们需要在title处添加图标,则App组件复用Card组件的方式如下:

  1. <!-- App组件:/slot-demo/src/App.vue -->
  2. <card :list="list" :title="title">
  3. <template v-slot:title="slotTitle">
  4. <i class="el-icon-guide"></i>{{slotTitle.titleValue}}
  5. </template>
  6. </card>

页面效果:

亦或者有些页面需要在展示内容时候不显示编号1、2、3

  1. <!-- App组件:/slot-demo/src/App.vue -->
  2. <card :list="list" :title="title">
  3. <template v-slot:text="slotItem">
  4. {{slotItem.itemValue.text}}
  5. </template>
  6. </card>

页面效果:

这里应该能想起来element table组件的实现方式,是不是也有点这样的意思呢

到这里或许有人会说这样的需求不用插槽也能实现,直接在Card组件中增加一些逻辑即可。这样的说法固然是可以实现功能,但是显然不是一个好办法。

因为组件的设计本身是希望拿来复用的,如果这个组件本身大部分实现是符合我们的需求的,只有一小部分不符合,我们首先应该想要的是去扩展该组件,而不是修改组件,这也是软件设计的思想:开放扩展,关闭修改。所以插槽的出现正是对组件的一种扩展,让我们可以更加灵活的复用组件。

废弃的插槽语法

关于以上所描述的插槽语法,均是vue 2.6.0以后的语法。在这之前,插槽的语法为slot(默认插槽或者具名插槽)slot-scope(作用域插槽)

默认插槽

  1. <!-- Card组件:/slot-demo/src/components/Card.vue -->
  2. <!-- 子组件的写法依然不变 -->
  3. <slot></slot>
  4. <!-- App组件:/slot-demo/src/App.vue -->
  5. <child>
  6. <template>
  7. <h1>欢迎关注小土豆</h1>
  8. </template>
  9. <child>
  10. <p>或者</p>
  11. <child>
  12. <template slot="default">
  13. <h1>欢迎关注小土豆</h1>
  14. </template>
  15. <child>

页面效果:

具名插槽

  1. <!-- Card组件:/slot-demo/src/components/Card.vue -->
  2. <!-- 子组件的写法依然不变 -->
  3. <!-- 插槽名称为:heading -->
  4. <slot name="heading"></slot>
  5. <!-- 插槽名称为:sub-heading -->
  6. <slot name="sub-heading"></slot>
  7. <!-- 插槽名称为:footer-text -->
  8. <slot name="footer-text"></slot>
  9. <!-- App组件:/slot-demo/src/App.vue -->
  10. <child>
  11. <template slot="heading">
  12. <h1>element-ui组件</h1>
  13. </template>
  14. <template slot="sub-heading">
  15. <p>这里是element-ui的部分组件介绍</p>
  16. </template>
  17. <template slot="footer-text">
  18. <p>出品@小土豆</p>
  19. </template>
  20. </child>

页面效果:

作用域插槽

  1. <!-- 子组件: /src/components/Child.vue -->
  2. <slot
  3. name="heading"
  4. v-bind:headingValue="heading">
  5. {{heading}}
  6. </slot>
  7. <!-- 父组件: /slot-demo/src/App.vue -->
  8. <child>
  9. <template slot="heading" slot-scope="headingValue" >
  10. <h1>element-ui组件</h1>
  11. headingValue = {{headingValue}}
  12. </template>
  13. </child>

页面效果:

总结

到这里本篇文章就结束了,内容非常简单易懂,可以是茶余饭后的一篇知识回顾。

最后我们在来做一个小小的总结:

近期文章

记一次真实的Webpack优化经历

JavaScript的执行上下文,真没你想的那么难

骨架屏(page-skeleton-webpack-plugin)初探

Vue结合Django-Rest-Frameword实现登录认证(二)

Vue结合Django-Rest-Frameword实现登录认证(一)

写在最后

如果这篇文章有帮助到你,️关注+点赞️鼓励一下作者

文章公众号首发,关注 不知名宝藏程序媛 第一时间获取最新的文章

笔芯️~

详解Vue中的插槽的更多相关文章

  1. 详解Vue中的computed和watch

    作者:小土豆 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.cn/user/2436173500265335 1. 前言 作为一名Vue ...

  2. 详解Vue中watch的高级用法

    我们通过实例代码给大家分享了Vue中watch的高级用法,对此知识点有需要的朋友可以跟着学习下. 假设有如下代码: <div> <p>FullName: {{fullName} ...

  3. 详解Vue中的nextTick

    Vue中的nextTick涉及到Vue中DOM的异步更新,感觉很有意思,特意了解了一下.其中关于nextTick的源码涉及到不少知识,很多不太理解,暂且根据自己的一些感悟介绍下nextTick. 一. ...

  4. 详解Vue中的虚拟DOM

    摘要: 什么是虚拟DOM? 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 Vue.js 2.0引入Virtual DOM,比Vue.js 1.0的初始渲染速度提升了2-4倍,并 ...

  5. 详解JavaScript中的原型

    前言 原型.原型链应该是被大多数前端er说烂的词,但是应该还有很多人不能完整的解释这两个内容,当然也包括我自己. 最早一篇原型链文章写于2019年07月,那个时候也是费了老大劲才理解到了七八成,到现在 ...

  6. 详解vue的数据binding原理

    自从angular火了以后,各种mv*框架喷涌而出,angular虽然比较火,但是他的坑还是蛮多的,还有许多性能问题被人们吐槽.比如坑爹的脏检查机制,数据binding是受人喜爱的,脏检查就有点…性能 ...

  7. 详解Vue 方法与事件处理器

      本篇文章主要介绍了详解Vue 方法与事件处理器 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 方法与事件处理器 方法处理器 可以用 v-on 指令监听 DOM 事件 ...

  8. 详解Vue的slot新用法

    摘要: 理解Vue插槽. 作者:前端小智 原文:vue 2.6 中 slot 的新用法 Fundebug经授权转载,版权归原作者所有. 为了保证的可读性,本文采用意译而非直译. 最近发布不久的Vue ...

  9. 详解vue 路由跳转四种方式 (带参数)

    详解vue 路由跳转四种方式 (带参数):https://www.jb51.net/article/160401.htm 1.  router-link ? 1 2 3 4 5 6 7 8 9 10 ...

随机推荐

  1. 华为交换机eNSP删除Vlan的详细步骤

    设备支持批量删除VLAN和单个删除VLAN两种方式: 单个删除VLAN10 <HUAWEI> system-view [HUAWEI] undo vlan 10 批量删除VLAN10到VL ...

  2. 新手上路A4——多JDK环境变量的配置

    目录 配置单个JDK的方法 配置2+JDK的方法 方法 补充 检查JDK版本是否切换成功 前面讲了如何选择Java版本. 以及JDK8和JDK11的下载安装配置 有想法的人就开始发动他们优秀的小脑袋瓜 ...

  3. 阿里云ECS服务器连接MongoDB

    第一次接触MongoDB,第一次部署.将一些步骤整理出来,希望以后会用到,也希望能帮组到有这方面需求的小伙伴. 设备说明: 服务器为阿里云ECS服务器,网络为专有网络VPC,Mango为买的阿里云Ma ...

  4. npm常用操作

    Npm常用操作 1. 淘宝镜像 1.1 npm临时使用淘宝镜像安装依赖包 npm i -g express --registry https://registry.npm.taobao.org 1.2 ...

  5. PyQt(Python+Qt)学习随笔:Qt中的部分类型QString、QList和指针、引用在PyQt中的实现方式

    老猿Python博文目录 老猿Python博客地址 在我们查阅Qt的文档资料时,可以看到Qt中的链表使用的是QList,字符串使用的是QString,但老猿在测试时发现这两个类型PyQt不支持,无法找 ...

  6. 刷题记录:[GWCTF 2019]枯燥的抽奖

    目录 刷题记录:[GWCTF 2019]枯燥的抽奖 知识点 php伪随机性 刷题记录:[GWCTF 2019]枯燥的抽奖 题目复现链接:https://buuoj.cn/challenges 参考链接 ...

  7. flask注册蓝图报错

    记录下这个我找了两天的坑... take no arguments() 这两天一直学习flask的时候,我把注册的蓝图,写成注册的form表单的 举个栗子 class TetsView(view.Me ...

  8. 庐山真面目之七微服务架构Consul集群、Ocelot网关集群和IdentityServer4版本实现

    庐山真面目之七微服务架构Consul集群.Ocelot网关集群和IdentityServer4版本实现 一.简介      在上一篇文章<庐山真面目之六微服务架构Consul集群.Ocelot网 ...

  9. Vue项目上线环境部署,项目优化策略,生成打包报告,及上线相关配置

    Node.js简介 Node.js是一个基于Chrome V8引擎的JavaScript运行环境,用来方便快速地搭建易于扩展的网络应用.Node.js使用了一个事件驱动.非阻塞式I/O的模型,使其轻量 ...

  10. Day5 - 01 函数及函数的调用概念

    函数就是最基本的一种代码抽象的方式.函数只需写一次,就可以多次调用.Python本身内置了很多有用的函数,可以直接调用. 调用函数    要调用一个函数,需要知道函数的名称和参数.可以通过help(x ...