从一个简单的 list 组件搞懂 Vue 插槽

需求

先假设咱们须要实现以下效果,并将其封装成一个组件,固然目的并非要真的封装组件,而是在这个过程当中学习 插槽 的使用以及优化的思路哈: html

一看到这个效果,有的小伙伴可能就要说了:这还不简单,反手就是一段代码甩上来

<template>
  <div>
    <div>{{ title }}</div>
    <hr>
    <div>
      <div v-for="(item, i) in List" :key="i">{{ item }}</div>
    </div>
  </div>
</template>
复制代码

而后在父组件中这样调用就 ok 了:编程

<template>
  <custom-list :title="title" :List="books" />
</template>

<script> import CustomList from './custom-list.js'; export default { components: { CustomList }, data() { return { title: '书籍列表', books: ['Dom编程艺术', '你不知道的Javascript', 'CSS世界', 'HTML入门教程'], }; }, }; </script>
复制代码

Emmm。。乍一看彷佛没有任何毛病,甚至数据变化了也是能够知足要求的。markdown

然而,这样就够了吗?需求老是善变的,好比说,有一天又要知足以下效果呢?学习

此时,这个组件就有点不够用了,没法应对各类需求的组件不是好组件。既然内容是多变的,那就不能固定,而应该是灵活的,最灵活的方式应该是将决定权交给使用者(父组件),使用者是最清楚内容应该是什么的,那具体怎么作呢?

插槽

这个时候就到咱们的 插槽 登场了。插槽容许咱们将子组件中的内容分发到父组件,由父组件决定子组件要渲染的内容。优化

普通插槽

仍是以上述组件为例,咱们须要将子组件中 title的内容分发出去,由父组件将内容传进来。此时咱们能够这样修改子组件:spa

<template>
  <div>
    <div>
      <slot></slot>
    </div>
    <hr>
    <div>
      <div v-for="(item, i) in List" :key="i">{{ item }}</div>
    </div>
  </div>
</template>
复制代码

而后在父组件中调用:code

<template>
  <custom-list :List="books">
    <span><icon type="waves"> 书籍列表</span>
  </custom-list>
</template>

<script> import CustomList from './custom-list.js'; export default { components: { CustomList }, data() { return { title: '书籍列表', books: ['Dom编程艺术', '你不知道的Javascript', 'CSS世界', 'HTML入门教程'], }; }, }; </script>
复制代码

这样的话,父组件中包裹的内容就会替代 <slot> 标签,因此最终的渲染结果就是这样的:component

<template>
  <div>
    <div>
      <span><icon type="waves"> 书籍列表</span>
    </div>
    <hr>
    <div>
      <div v-for="(item, i) in List" :key="i">{{ item }}</div>
    </div>
  </div>
</template>
复制代码

顺便提一嘴:在 <slot> 标签中也是能够放置内容的,如:<slot> 标题 </slot>, 这表示插槽的默认内容,只有当父组件没有经过插槽传递内容的时候,才会显示该默认内容。orm

同理咱们的 content 的内容也是须要分发出去的,因而咱们再修改:htm

<template>
  <div>
    <div>
      <slot></slot>
    </div>
    <hr>
    <slot></slot>
  </div>
</template>
复制代码

这样一来又有问题了:咱们定义了两个 怎么去区分它们的内容呢,总不能靠书写顺序吧?这显然是不靠谱的。因而,就有了 具名插槽

具名插槽

顾名思义就是带名字的插槽,便于多个插槽之间的区分。而后咱们继续修改:

<template>
  <div>
    <div>
      <slot name="title"></slot>
    </div>
    <hr>
    <slot name="content"></slot>
  </div>
</template>
复制代码

给每一个插槽定义一个 name 属性,而后咱们在父组件传值的时候带上对应插槽的 name 就好了。

<template>
  <custom-list>
    <template v-slot:title>
      <span><icon type="waves"> 书籍列表</span>
    <template>
    <template v-slot:content>
      <div>
        <div v-for="(item, i) in book" :key="i">
          <icon :type="item.icon">{{ item.name }}
        </div>
      </div>
    </template>
  </custom-list>
</template>

<script> import CustomList from './custom-list.js'; export default { components: { CustomList }, data() { return { title: '书籍列表', books: [ { name: 'Dom编程艺术',icon: 'face' }, { name: '你不知道的Javascript', icon: 'favorite' }, { name: 'CSS世界', icon: 'av_timer'}, { name: 'HTML入门教程', icon: 'star_half' }, ], }; }, }; </script>
复制代码

在向具名插槽提供内容的时候,咱们能够在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。

这下可好了,连数据都不用传了,List 在子组件根本用不到,数据直接在父组件中就渲染了,并且每次使用这个组件的时候都要写一遍 v-for,那为何不把 v-for 写在子组件中呢?这样就只须要写一次了。 那咱们再来修改子组件:

<template>
  <div>
    <div>
      <slot name="title"></slot>
    </div>
    <hr>
    <div>
      <div v-for="(item, i) in List">
        <slot name="item"></slot>
      </div>
    </div>
  </div>
</template>
复制代码

这样又有问题了:我要显示 item 可是 item 是子组件中的数据,当父组件经过 v-slot 传递内容进来的时候,<slot>{{ item }}</slot> 的内容就会被传进来的内容替换了。这咋搞呢?思考一下,若是咱们将数据给父组件,在父组件中显示不就解决了么?那么这个时候就要轮到 做用域插槽 登场了。

做用域插槽

做用域插槽容许咱们将子组件的数据传给父组件,这不正好解决了上面的问题么?下面咱们再调整子组件

<template>
  <div>
    <div>
      <slot name="title"></slot>
    </div>
    <hr>
    <div>
      <div v-for="(item, i) in List">
        <slot name="item" :row="item"></slot>
      </div>
    </div>
  </div>
</template>
复制代码

此时咱们将每一个 item 的值经过 row 属性传给了 <slot>, 而后咱们在父组件中取出这个值:

<template>
  <custom-list>
    <template v-slot:title>
      <span><icon type="waves"> 书籍列表</span>
    <template>
    <!--实际传的数据格式为: { row:{ name: xx, icon: xx } },因此这里可使用结构赋值-->
    <template v-slot:item="{ row }">
      <icon :type="row.icon">{{ row.name }}
    </template>
  </custom-list>
</template>
复制代码

这样咱们的组件就完成了,不只灵活性更高,代码健壮性也更好。 总得来讲,插槽仍是很好用的,特别是在封装组件的时候。