Detailed explanation of slot usage in Vue: default slot, named slot, scoped slot

Author: Little Tudou
Blog Garden: www.cnblogs.com/HouJiao/Nuggets
: juejin.im/user/243617…

what is a slot

In daily project development, when we write a complete component, it is inevitable to refer to some 外部组件or 自定义组件.

With this 引用关系, we can call them 父组件or 子组件, at the same 父子组件time, there are many communication methods, such as passing data through propsto 子组件, or through $emit, $parentcalling 父组件methods.

Below is an example of a very simple 父组件citation 子组件.

<!-- 子组件: /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>

<!-- 省略其它代码 -->

复制代码

Then we Appreference Childthe component in the component.

<!-- 父组件: /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>
复制代码

Finally, run the project, 子组件and the content of the content is successfully referenced and displayed on the page.

So if we have such a requirement now: while referencing Childthe component, we want to insert a piece of content in Childthe component 指定位置: <h1> 欢迎大家关注小土豆 </h1>.

If we directly write the content <child></child>inside, it will not take effect.

<!-- 父组件: /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>
复制代码

It can be seen that the expected effect is not achieved:

In order to solve problems like this, this thing Vuewas designed slot. slotThe translation is called 插槽, and it can also be called Vuethe content distribution mechanism. Its main function is to 子组件insert 指定位置a piece of content into the . This content can be HTMLor 其他的组件.

默认插槽

在前面一节内容里,我们提出了一个需求:在引用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往指定的插槽填充内容。

#namev-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组件中展示的titlelist数据由父组件传入。

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

<!-- 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组件本身并不复杂,就是展示titlelist里面的数据。但凡是有相同需求的都可以通过复用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组件展示titlelist的位置分别添加了对应的具名插槽,并且通过v-bindtitleitemlist循环出来的数据)传递给了父组件

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

假如我们需要在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>
复制代码

页面效果:

总结

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

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

写在最后

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

笔芯❤️~

Author: Little Tudou
Blog Garden: www.cnblogs.com/HouJiao/Nuggets
: juejin.im/user/243617…

Guess you like

Origin juejin.im/post/7208015274079469627