Vue slot 插槽用法:自定义列表组件

更新日期: 2021-08-11阅读: 1.6k标签: 插槽

vue 框架的插槽(slot)功能相对于常用的 v-for, v-if 等指令使用频率少得多,但在实现可复用的自定义组件时十分有用。例如,如果经常使用前端组件库的话,就会经常看到类似的用法:

<card>
	<template slot="title">定制卡片组件的标题栏,例如插入icon、操作按钮等</template>
</card>

之前在写前端时,发现产品原型的多个页面中多次出现了基本一致的信息栏,如下图。如果只在一个页面中出现一两次,复制几遍列表代码,写一套样式也关系不大;但在多个页面中来回复制粘贴就很麻烦,增加无效代码量,以后也不好修改(众所周知,前端 UI 修改并不罕见)。因此将这个信息栏抽象成一个组件,可以多次复用,实现内容与样式分离。接下来借这个例子分享一下 Vue 插槽的几种主要用法。


基本用法

默认插槽

首先新建 InfoCard.vue 组件,写好基本的模板结构和样式:上面一条标题栏,下面一个列表(项目中用的是 antd 组件库中的组件,只是样式,不影响理解)。css 不重要就不贴了。

<!-- InfoCard.vue -->
<template>
  <div class="side-card">
    <div class="side-card-title">这里是标题</div>
    <a-list>
      <a-list-item>
        <slot></slot>
      </a-list-item>
    </a-list>
  </div>
</template>

在页面中引入组件后可以在模板中用标签使用:

<!-- index.vue -->
<info-card>Hello</info-card>

由于组件中只有一个 <slot> 元素(也就是“插槽”),标签内的内容就会被“插入”插槽对应的位置:


具名插槽

如果组件中有不止一个插槽,就需要通过名字来区分。

  • 在组件中,使用 <slot name="xx"> 属性指定插槽的名字
  • 在页面中,使用 <template slot="xx"> 属性将内容分发到对应的插槽
<!-- InfoCard.vue -->
<div class="side-card">
  <div class="side-card-title" v-if="!hideTitle">
    <slot name="title"></slot>
  </div>
  <a-list>
    <a-list-item>
      <slot name="content"></slot>
    </a-list-item>
  </a-list>
</div>


<!-- index.vue -->
<info-card>
  <template slot="title">
    <p>Hello <a-icon type="smile" /></p>
  </template>
  <template slot="content"> world </template>
</info-card>


缺省内容

有时候组件的可变部分有默认值,并不必须在使用时指定(例如默认提示语)。在组件的 <slot> 标签内部的内容就是该插槽的缺省内容,如果在使用时没有传入相应内容,将使用缺省内容进行渲染。

在这个例子中,标题部分多数情况下是纯文本,少数情况下才需要使用 html 进行定制(例如包含操作按钮)。因此可以配合组件的传入参数,让标题定义变得更简洁,不需要为了一行文本去写整个标签。(顺便增加一个参数可以隐藏标题栏。)

提示:组件传参的时候注意区分title="xxx" 和 :title="xxx",平时写多了冒号容易手滑。加冒号是简写的 v-bind 指令,这个“xxx”代表的是 data 中一个叫做 xxx 的变量;不加冒号的才是传入字符串“xxx”作为参数 title 的值。
<!-- InfoCard.vue -->
<div class="side-card">
  <div class="side-card-title" v-if="!hideTitle">
    <slot name="title">{{ title }}</slot>
  </div>
  <a-list>
    <a-list-item>
      <slot name="content"></slot>
    </a-list-item>
  </a-list>
</div>


<!-- index.vue -->
<info-card title="Hello">
  <template slot="content">world</template>
</info-card>

作用域

列表组件应该接收一个数组作为参数,使用 v-for 循环显示,并且每个列表项的具体内容由页面传入的插槽内容决定(因为不同列表里的对象不一致)。

<a-list>
  <a-list-item v-for="item in items" :key="item.id">
    <slot name="content"></slot>
  </a-list-item>
</a-list>

但是如果在页面中这样使用,会产生报错 Cannot read property 'tag' of undefined。

<info-card title="Hello" :items="hotTags">
  <template slot="content">
    <a-tag># {{ item.tag }}</a-tag>
    <span class="number">{{ item.count }}</span>
  </template>
</info-card>

产生错误的原因在于,父页面插槽中的内容先在父页面中渲染,之后才整体插入子组件的插槽;而不是先插入 HTML 后再一起渲染。很显然,items、item 都是定义在子组件中的变量数据,在父组件中没有定义,自然也无法访问(父页面中的数据是 hotTags)。

插槽 prop

这里使用的是 Vue 2.6.0 起更新的语法,原来的作用域插槽 slot-scope 属性已弃用

页面传递给子组件的参数作用域在子组件内部,而列表项的内容需要在父页面中定义;因此,需要一种在父组件访问子组件数据的机制。这就是插槽 prop 的作用。


在子组件的 <slot> 标签中使用 v-bind 绑定的属性就是插槽 prop(这里为了清晰才区分命名了 itemprop 和 item,其实实际用的时候全命名成一样的即可,省的倒来倒去)。

页面使用组件时,通过命令 v-slot:name="slotProps" 即可通过 slotProps 访问 name 插槽中绑定的插槽 prop。

<!-- InfoCard.vue -->
<a-list>
  <a-list-item v-for="item in items" :key="item.id">
    <slot name="content" :itemprop="item">
      {{ item }}
    </slot>
  </a-list-item>
</a-list>


<!-- index.vue -->
<info-card hideTitle :items="hotTags">
  <template v-slot:content="props">
    <a-tag># {{ props.itemprop.tag }}</a-tag>
    <span class="number">{{ props.itemprop.count }}</span>
  </template>
</info-card>


注意:如前文所述,插槽内容是在父页面中渲染的。因此其中元素的样式(例如这里 a-tag 的样式)也应该定义在父页面中。

简写

  • v-slot: 指令可以简写为 
  • 可以使用ES2015 解构解析插槽 prop 中的各个属性,更加清晰简洁
<template #content="{ itemprop }">
  <a-tag># {{ itemprop.tag }}</a-tag>
  <span class="number">{{ itemprop.count }}</span>
</template>

结语

以上是借助自定义表单组件案例对 Vue 插槽基本用法的介绍,希望对你有所帮助,如有疏漏欢迎留言指正讨论。

文末附上开头图片中信息栏案例的大部分实现代码,可以对照进行参考。

参考资料:Vue slot

附录

以下是 InfoCard.vue 的全部代码:

<template>
  <div>
    <div v-if="!hideTitle">
      <slot name="title">{{ title }}</slot>
    </div>
    <a-list>
      <a-list-item v-for="item in items" :key="item.id">
        <slot name="content" :item="item">
          {{ item }}
        </slot>
      </a-list-item>
    </a-list>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: '',
    },
    hideTitle: {
      type: Boolean,
      default: false,
    },
    items: {
      type: Array,
      required: true,
    },
  },
}
</script>

<style lang="less" scoped>
.side-card {
  border-radius: 4px;
  background-color: @item-background;
  .side-card-title {
    height: 36px;
    line-height: 36px;
    padding: 0 20px;
    color: #ffffff;
    border-radius: 4px 4px 0 0;
    background: linear-gradient(90deg, #1375ff 0%, #a4fffa 149.57%);
    display: flex;
    justify-content: space-between;
  }
  .ant-list {
    padding: 0 20px;
    .ant-list-item {
      display: flex;
    }
  }
}
</style>

以下是实现开头三个信息栏的父页面代码(缺少一些 icon):

<list-card hideTitle :items="myData" class="side-card">
  <template #content="{ item }">
    <span>{{ item.title }}</span>
    <a-tag class="number">{{ item.count }}</a-tag>
  </template>
</list-card>

<list-card title="本周热搜 TOP5" :items="hotTags" class="side-card">
  <template #content="{ item }">
    <a-tag># {{ item.tag }}</a-tag>
    <span class="number">{{ item.count }}</span>
  </template>
</list-card>

<list-card :items="suggestScholars" class="side-card">
  <template slot="title">
    <span>可能感兴趣的人</span>
    <span>换一批</span>
  </template>
  <template #content="{ item }">
    <div class="scholar">
      <div class="name">
        <h2>{{ item.name }}</h2>
        <a-button v-if="item.followed" shape="round" class="btn">
          已关注
        </a-button>
        <a-button v-else type="primary" shape="round" class="btn">
          关注
        </a-button>
      </div>
      <div>研究领域:{{ item.field }}</div>
      <div>{{ item.institution }} · {{ item.position }}</div>
    </div>
  </template>
</list-card>
来自:https://www.cnblogs.com/skuld-yi/archive/2021/08/10/15125009.html


链接: https://fly63.com/article/detial/10625

Vue 技能进阶:使用设计模式写出优雅的前端代码

本文针对 Vue 中如何控制组件子树之外的东西,探讨了四种解决方案,并展示了每种解决方案的优缺点。希望读者能从中受到启发。问你个问题,以前你可能从来没想过:有没有办法从子组件填充父组件插槽?

vue插槽slot和slot-scope

插槽我对他的理解就是父组件的东西插到子组件的<slot></slot>里面,也不知道这样理解对不对,方便自己记忆;作用域插槽我对他的理解就是数据在子组件里面,父组件可以用

一个案例搞懂 Vue.js 的作用域插槽

作用域插槽是 Vue.js 中一个很有用的特性,可以显著提高组件的通用性和可复用性。问题在于,它实在不太好理解。尝试搞清楚父子作用域之间错综复杂的关系,其痛苦程度不亚于求解一个棘手的数学方程。

Vue.js 中的无渲染行为插槽

在本文中我们讨论 Vue 中的无渲染插槽模式能够帮助解决哪些问题。在 Vue.js 2.3.0 中引入的作用域插槽显著提高了组件的可重用性。无渲染组件模式应运而生,解决了提供可重用行为和可插入表示的问题。

vue具名插槽、作用域插槽的新写法

有时让插槽内容能够访问子组件中才有的数据是很有用的。但是由于子组件的作用域在子组件,而父组件的作用域在父组件,这样一来,父组件就访问不到子组件的信息了,但是我们又不想用$emit发送事件去传递信息

深入了解Vue.js的作用域插槽

作用域槽是Vue.js的一个有用特性,它可以使组件更加通用和可重用。唯一的问题是它们很难理解!试着让你的头在父母和孩子的范围内交织,就像解决一个棘手的数学方程。

Vue 2.60 中, 插槽新增的v-slot

注意v-slot只能添加在<template>上(只有一种例外情况),这一点和已经废弃的slotattribute不同。有时让插槽内容能够访问子组件中才有的数据是很有用的。例如,设想一个带有如下模板的<current-user>组件:

如何通过带有Vue插槽的组件传递HTML内容

这篇文章将向您介绍如何使用Vue插槽在Vue.js中将数据从父组件传递到子组件。这篇文章适合所有阶段的开发人员(包括初学者)。

如何使用Vue中的嵌套插槽(包括作用域插槽)

最近我弄清楚了如何递归地实现嵌套插槽,包括如何使用作用域插槽来实现。起因是我想看看是否可以构建一个复制v-for指令但仅使用template组件。

在Vue中,如何从插槽中发出数据

我们知道使用作用域插槽可以将数据传递到插槽中,但是如何从插槽传回来呢?将一个方法传递到我们的插槽中,然后在插槽中调用该方法。 我信无法发出事件

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!