Vue组件插槽slot

269 阅读4分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路。

实现一个功能:即展示三张卡片,第一张卡片展示图片,第二张卡片展示文字、第三张卡片展示视频。

不使用插槽

创建一个卡片组件cardInfo.vue,该组件为子组件。在父组件里面调用三次即可,数据通过props方式传递。

这里先写一个只传文字的三张卡片。

卡片组件cardInfo.vue

<template>
  <div class="card-info">
    <div>{{ title }}</div>
    <ul>
      <li v-for="(item, index) in dataList" :key="index">
        {{ item }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "cardInfo",
  props: {
    title: {
      type: String,
    },
    dataList: {
      type: Array,
    },
  },
};
</script>

<style scoped lang="less">
.card-info {
  margin: 10px;
  padding: 10px;
  background-color: aliceblue;
}
</style>

展示卡片的父组件

<template>
  <div class="card-info-container">
    <card-info :title="'图片'" :dataList="pictureCard"></card-info>
    <card-info :title="'文字'" :dataList="textCard"></card-info>
    <card-info :title="'视频'" :dataList="vedioCard"></card-info>
  </div>
</template>

<script>
import CardInfo from "@/components/cardInfo";
export default {
  components: { CardInfo },
  name: "oneComponent",
  data() {
    return {
      pictureCard: ["图片1", "图片1", "图片1"],
      textCard: ["文字2", "文字2", "文字2"],
      vedioCard: ["视频3", "视频3", "视频3"],
    };
  },
};
</script>

<style scoped  lang="less">
.card-info-container {
  display: flex;
  justify-content: center;
}
</style>

image.png

上面代码当传递相同类型的数据时,可以直接通过props传值过去,这样直接在子组件接收数据展示即可。

但是,如果第一张卡片为图片,第三张卡片为视频,还是通过props传值过去吗,然后在子组件判断如何展示?代码如下:

cardInfo.vue

<template>
  <div class="card-info">
    <div>{{ title }}</div>
    <ul v-if="title == '文字'">
      <li v-for="(item, index) in dataList" :key="index">
        {{ item }}
      </li>
    </ul>
    <img
      v-if="title == '图片'"
      src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.jiemian.com%2F101%2Foriginal%2F20190830%2F15671318237617400_a700x398.jpg&refer=http%3A%2F%2Fimg3.jiemian.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1655391765&t=67b61d217a2eb21707deaf1688162537"
      alt="loading"
    />
    <video
      controls
      v-if="title == '视频'"
      src="https://www.baidu.com/link?url=Czb7DP24dwm9DJIJifPozod-AYte9FklWuzam9-M-bEjcxxecWuFbad9_FFJHFqYimkgy162A5A9CAGZTQGPs_&wd=&eqid=ffa46355000132e3000000046283b9b2"
    ></video>
  </div>
</template>

<script>
export default {
  name: "cardInfo",
  props: {
    title: {
      type: String,
    },
    dataList: {
      type: Array,
    },
  },
};
</script>

<style scoped lang="less">
.card-info {
  width: 100px;
  margin: 10px;
  padding: 10px;
  background-color: aliceblue;
}
img{
  width: 100%;
}
video{
  width: 100%;
}
</style>

image.png

直接使用v-if判断,这样也能实现功能。

但是,如果添加一个文字加图片的卡片,那么还需要修改cardInfo组件。如果很多人使用该组件,那么就需要根据需求多次修改该组件,很麻烦。所以,可以使用插槽来实现。这样就不用修改cardInfo组件,直接在父组件修改,即使多人操作,也不会麻烦。

插槽作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信方式,适用于父组件=>子组件。

分类:默认插槽、具名插槽、作用域插槽。

默认插槽

默认插槽,就是将父组件写在该组件标签的标签体内的结构和数据插入子组件中,默认插槽只有一个插入位置,要插入的html结构和data数据必须在父组件中,css在父组件和子组件中都可以写。

cardInfo.vue

<template>
  <div class="card-info">
    <!-- 默认插槽 -->
    <slot>默认值,当使用时没有传递具体结构时,会出现该文字。</slot>
  </div>
</template>

<script>
export default {
  name: "cardInfo",
};
</script>

<style scoped lang="less">
.card-info {
  width: 100px;
  margin: 10px;
  padding: 10px;
  background-color: aliceblue;
}
img{
  width: 100%;
}
video{
  width: 100%;
}
</style>

oneComponent.vue

<template>
  <div class="card-info-container">
  
    <card-info>
      <div>图片</div>
      <img
        src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.jiemian.com%2F101%2Foriginal%2F20190830%2F15671318237617400_a700x398.jpg&refer=http%3A%2F%2Fimg3.jiemian.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1655391765&t=67b61d217a2eb21707deaf1688162537"
        alt="loading"/>
    </card-info>
    
    <card-info>
      <div>文字</div>
      <ul>
        <li v-for="(item, index) in textCard" :key="index">
          {{ item }}
        </li>
      </ul>
    </card-info>
    
    <card-info>
      <div>视频</div>
      <video
        controls
        src="https://www.baidu.com/link?url=Czb7DP24dwm9DJIJifPozod-AYte9FklWuzam9-M-bEjcxxecWuFbad9_FFJHFqYimkgy162A5A9CAGZTQGPs_&wd=&eqid=ffa46355000132e3000000046283b9b2"/>
    </card-info>
    
  </div>
</template>

<script>
import CardInfo from "@/components/cardInfo";
export default {
  components: { CardInfo },
  name: "oneComponent",
  data() {
    return {
      textCard: ["文字2", "文字2", "文字2"]
    };
  },
};
</script>

<style scoped  lang="less">
.card-info-container {
  display: flex;
  justify-content: center;
}
</style>

上面代码实现了默认插槽。这样,不管是图片文字还是其他展示,都直接在使用cardInfo组件时,将需要插入的内容写在组件标签的标签体内。

image.png

具名插槽

默认插槽只能有一个,如果想要设置几个插槽,则可以使用具名插槽。具名插槽就是给插槽取名字,在使用插槽时,根据名字使用相关插槽。

在定义插槽时,使用name="XXX"给插槽起名。在使用插槽时,使用slot="XXX"使用具体的插槽。

定义插槽的组件

...
<slot name="center">默认值,当使用时没有传递具体结构时,会出现该文字。</slot>
<slot name="footer">默认值,当使用时没有传递具体结构时,会出现该文字。</slot>
...

父组件

<template>
  <div class="card-info-container">
  
    <card-info>
      <div slot="center">图片</div>
      <img
        slot="footer"
        src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg3.jiemian.com%2F101%2Foriginal%2F20190830%2F15671318237617400_a700x398.jpg&refer=http%3A%2F%2Fimg3.jiemian.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1655391765&t=67b61d217a2eb21707deaf1688162537"
        alt="loading"/>
    </card-info>
    
    <card-info>
      <div slot="center">文字</div>
      <ul slot="footer">
        <li v-for="(item, index) in textCard" :key="index">
          {{ item }}
        </li>
      </ul>
    </card-info>
    
    <card-info>
      <div slot="center">视频</div>
      <video
        slot="footer"
        controls
        src="https://www.baidu.com/link?url=Czb7DP24dwm9DJIJifPozod-AYte9FklWuzam9-M-bEjcxxecWuFbad9_FFJHFqYimkgy162A5A9CAGZTQGPs_&wd=&eqid=ffa46355000132e3000000046283b9b2"/>
    </card-info>
    
  </div>
</template>

<script>
import CardInfo from "@/components/cardInfo";
export default {
  components: { CardInfo },
  name: "oneComponent",
  data() {
    return {
      textCard: ["文字2", "文字2", "文字2"]
    };
  },
};
</script>

<style scoped  lang="less">
.card-info-container {
  display: flex;
  justify-content: center;
}
</style>

slot里面有多个便签时,可通过div包裹,在div处使用slot:

...
<div slot="footer">
    <ul>
      <li v-for="(item, index) in textCard" :key="index">
        {{ item }}
      </li>
    </ul>
    <span>查看详情</span>
</div>
...

若有很多层级,也可以使用template包裹,template不会被渲染成DOM:

<template slot="footer">
    <ul>
      <li v-for="(item, index) in textCard" :key="index">
        {{ item }}
      </li>
    </ul>
    <span>查看详情</span>
</template>

使用template时,也可以通过v-slot绑定具体插槽:

<template v-slot:footer>
    <ul>
      <li v-for="(item, index) in textCard" :key="index">
        {{ item }}
      </li>
    </ul>
    <span>查看详情</span>
</template>

通过具名插槽,就可以在定义插槽的组件根据插槽名设置多个插槽。在使用插槽的组件通过插槽名使用对应的插槽。

作用域插槽

作用域插槽与其他插槽的区别就在于,作用域插槽的数据在定义插槽组件的自身,但是根据数据生成的结构需要组件的使用者决定(数据在定义插槽的cardInfo组件中,但是使用数据遍历出来的结构在父组件中)。

定义插槽的组件cardInfo.vue

...
<!-- 作用域插槽,数据在该组件中,通过在slot里面绑定数据传给插槽的使用者。 -->
<slot :dataText="textCard">默认值,当使用时没有传递具体结构时,会出现该文字。</slot>
...
data(){
    return{
        textCard: ["文字2", "文字2", "文字2"]
    }
}
...

使用插槽的组件

<card-info>
  <!-- 作用域插槽,要使用template,通过scope接收。 -->
  <template scope="dataList">
    <ul>
      <li v-for="(item, index) in dataList.dataText" :key="index">
        {{ item }}
      </li>
    </ul>
    <span>查看详情</span>
  </template>
</card-info>

其中,dataList是一个对象,包含slot传过来的值。

如果slot传递多个值:

...
<slot :dataText="textCard" :dataCard="cardInfo">默认值,当使用时没有传递具体结构时,会出现该文字。</slot>
...
data(){
    return{
      textCard: ["文字2", "文字2", "文字2"],
      cardInfo: {
        name:'cardInfo',
        desc:'cardInfo desc'
      },
    }
}
...

此时,使用插槽的地方接收的数据为:

image.png

在slot中绑定的数据dataText和dataCard为属性名,传递的值textCard和cardInfo为属性值。