通过UI库深入了解Vue的插槽的使用技巧(片尾有彩蛋)

简介: Vue官网对于插槽的介绍比较简略,插槽本身也比较“烧脑”,很容易看晕,我就一直没看懂,直到 使用了element-plus的组件的插槽。其实我们可以换一个角度来理解插槽,就会豁然开朗了。

技术栈

  • vite
  • vue3
  • element-plus

从父子组件的传值开始

父子组件传值可以通过 prosp + emit 来实现,虽然 props 可以传递各种类型,但是却不能传递组件(包括HTML),这样灵活度就差了一些。那么怎么办呢?为了提高灵活性,Vue 提供了插槽功能。

插槽可以分为:插槽、具名插槽、作用域插槽如果不明所以的话,可以换一种名称:匿名插槽、命名插槽、可传参插槽

匿名插槽

如何理解插槽呢?可以先看看div,div是一个容器,里面可以放各种HTML标签,同时也可以放各种组件。

那么我们可以把div内部的标签、组件视为插槽内容,同理,我们也可以把 select 内部的 option 也视为插槽内容。

我们可以用匿名插槽的方式,写一个my-div的组件。

  • 子组件 ./comp/my-div.vue
<div style="margin: 10px;padding: 10px; border:1px solid orange;">
    匿名插槽:
    插槽前<br><br>
    <slot>没有设置插槽</slot>
    <br><br>
    插槽后
  </div>

子组件设置一个 slot 标签,slot 可以理解为是一种“插值”,表示父组件的插槽在这个位置被渲染,然后在其前后可以加入子组件自己的内容。

slot 里面是“备用内容”,如果父组件没有设置插槽的话,“备用内容”会被渲染,否则会被忽略。

  • 父组件

我们看一看在父组件里面的使用情况:

import myDiv from'./comp/my-div.vue'
匿名插槽<br>
  设置文本框作为插槽内容:
  <my-div>
    <input type="text" placeholder="父组件的插槽">
  </my-div>
  <br>
  没有设置插槽内容:
  <br>
  <my-div></my-div>
  • 看看效果

126.png

匿名插槽

这样就实现了一个简单的具有插槽功能的组件,当然这个组件是为了插槽而插槽,并没有没有实际意义。

那么插槽在实际项目里可以有哪些作用呢?我们可以参考一下UI库的组件,他们有很多插槽的实际应用,比如 el-input、el-table等。

具名插槽。

“具名”是个啥意思?感觉用“命名插槽”更好理解一些。

  • 如果一个组件只有一个插槽,那么不用写名称,Vue会使用默认名称:default 。
  • 如果一个组件有多个插槽的话,那么就需要起名来区分不同的插槽。

el-input 提供了prefix、suffix、prepend、append四个插槽,就是采用了命名插槽的方式。

我们来看一下官网的例子:

<el-input v-model="input1" placeholder="Please input">
      <template #prepend>Http://</template>
    </el-input>
    <el-input v-model="input2" placeholder="Please input">
      <template #append>.com</template>
    </el-input>
  • # 是 v-lot: 的简写形式,类似于 “v-bind:” 简写为 “:”,“v-on:” 简写为 “@”
  • prepend 在文本框的前面放置一个插槽,比如 http://
  • append 在文本框的后面方式一个插槽,比如 .com

这样可以方便输入URL地址。其实如果 append 放置一个 el-autocomplete 的话,可以更灵活的设置域名后缀。

手写一个命名插槽

还是手写一个命名插槽,看一下子组件的实现方式。

  • 子组件  ./comp/my-div-name.vue
<div style="margin: 10px;padding: 10px; border:1px solid rgba(61, 67, 155, 0.692);">
    <slot name="header">我来组成头部</slot>
    插槽中间内容
    <slot name="footer">我来组成结尾</slot>
  </div>

实现具名插槽的方式很简单,用 name 属性设置插槽的名称即可。

  • 父组件的调用
import myDivName from'./comp/my-div-name.vue'
<my-div-name>
    <template v-slot:header>
      <h1>这是头部</h1>
    </template>
    <template #footer>
      <p>这是结尾</p>
    </template>
  </my-div-name>

父组件需要用 template 限定具名插槽内容的范围,我们来看看效果:

127.png

具名插槽

作用域插槽

插槽是父组件的,不是子组件的,父组件可以完全操作插槽里的组件。但是子组件只能规定插槽的渲染位置,其他的就不能操作了,这样的话还是有些不够灵活,于是出现了作用域插槽。

作用域插槽的目的是解决父组件、子组件、插槽之间的数据通讯的问题。

还是看看UI库组件 el-table 的插槽 。

父组件设置列表数据,传递给子组件,子组件渲染 table 表格。为了更灵活,组件提供了自定义列的功能,采用的就是作用域的插槽。

看一下官网示例:

<el-table :data="tableData" style="width: 100%">
    <el-table-column label="Date" width="180">
      <template #default="scope">
        <span style="margin-left: 10px">{{ scope.row.date }}</span>
      </template>
    </el-table-column>
</table>
  • scope 就是子组件传递出来的数据集合,包含row、column、$index等属性。
const tableData = reactive([
        {
          date: '2016-05-03',
          name: 'Tom',
          address: 'No. 189, Grove St, Los Angeles',
        },
        {
          date: '2016-05-02',
          name: 'Tom',
          address: 'No. 189, Grove St, Los Angeles',
        },
        {
          date: '2016-05-04',
          name: 'Tom',
          address: 'No. 189, Grove St, Los Angeles',
        },
        {
          date: '2016-05-01',
          name: 'Tom',
          address: 'No. 189, Grove St, Los Angeles',
        }
  ])
  • tableData:父组件定义数据列表,通过 data 属性传递给子组件。

这里的 scope 的数据流程是这样的:父组件 =》子组件 =》插槽。

为啥要绕一圈呢?虽然父组件可以直接给插槽设置值,但是由于 tr 是循环出来的,父组件无法获知循环到哪一行了,所以需要子组件告知循环行数,这个信息就是通过作用域插槽来实现的,我们可以做一个简单的示例。

手撸一个简单的作用域插槽

  • 子组件 ./comp/my-table.vue
<div>
    <table>
      <tr>
        <th>标题一</th>
        <th>标题二</th>
        <th>自定义</th>
      </tr>
      <tr v-for="(item, index) in data"
        :key="index"
      >
        <td>{{item.t1}}</td>
        <td>{{item.t2}}</td>
        <td>
          <slot name="td"
            :row="item"
            :$index="index"
          ></slot>
        </td>
      </tr>
    </table>
  </div>

第三列设置一个具名插槽,通过row、$index 传递数据。

const props = defineProps({
    data: Array
  })

设置一个属性,接收列表数据。

  • 父组件调用
import myTable from'./comp/my-table.vue'
  const data = reactive([
    { t1: '11', t2: '12', t3: '13' },
    { t1: '21', t2: '22', t3: '23' },
    { t1: '31', t2: '32', t3: '33' }
  ])
<my-table :data="data">
    <template #td="scope">
      自定义列:{{scope}}
    </template>
  </my-table>

可以看到数据的传递。

子组件的插槽,先起个名字,就叫做“td”好了,不要纠结名称,俺有起名困难症。

然后用 row 属性传递行的数据,用 $index 传递遍历到第几行的数据。

这样一个简单的作用域插槽就搞定了。当然只是一个示例,还是没有啥实际意义。

那么有实际意义的是什么样子的呢?还记得标题吗?我可不是标题党,彩蛋马上就来。


片尾彩蛋


现在流行用 json 来渲染组件,还是用 el-table 举例,我们可以定义一个 json,来描述表格列的情况,比如:

{
  "itemMeta": [
    {
      "prop": "name",
      "label": "姓名",
      "width": 140,
      "align": "center",
      "header-align": "center"
    },
    {
      "prop": "age",
      "label": "年龄",
      "width": 140,
      "align": "center",
      "header-align": "center"
    },
    {
      "prop": "mobile",
      "label": "电话",
      "width": 140,
      "align": "center",
      "header-align": "center"
    },
    {
      "prop": "url",
      "label": "URL",
      "width": 140,
      "align": "center",
      "header-align": "center"
    }
  ]
}

然后遍历 el-table-colmun 设置属性,这样就可以实现动态渲染 table 的功能。这样虽然很方便,但是自定义列呢?如果不支持插槽的话,那么灵活性就差了一些。

鱼和熊掌能不能兼得呢?既然都写到这里了,那么肯定可以兼得

做一个默认规则

自定义列的插槽名称格式:td_{字段名称}。也就是说 td_开头的视为自定义列的插槽,加上前缀可以避免和 el-table 自带的具名插槽冲突。

然后封装一下 el-table

建立一个组件 ./comp/my-table-json.vue

import { useSlots } from'vue'
  const props = defineProps({
    colInfo: Object,
    data: Array
  })
  // 获取插槽信息
  const slots = useSlots()
  // 获取列的描述信息
  const colInfo = props.colInfo
  // 检查插槽,设置名称
  colInfo.forEach(col => {
    const _slotName = 'td_' + col.prop
    if (typeof slots[_slotName] === 'function') {
      // 有插槽
      col.slotName = _slotName
    } else {
      // 没有插槽
      col.slotName = ''
    }
  })

定义属性,接收数据和列的描述。然后获取插槽的信息,设置列是否需要加载插槽。

<el-table :data="data" style="width: 100%">
    <template
      v-for="(item, index) in colInfo"
      :key="index"
    >
      <!--不带插槽的列-->
      <el-table-column
        v-if="item.slotName == ''"
        v-bind="item"
      >
      </el-table-column>
      <!--带插槽的列-->
      <el-table-column
        v-else
        v-bind="item"
      >
        <template #default="scope">
          <slot :name="item.slotName" v-bind="scope"></slot>
        </template>
      </el-table-column>
    </template>
  </el-table>

遍历列的描述信息,判断是否需要加载插槽,如果需要插槽的话,设置插槽并且传递 scope 数据。

父组件的调用

父组件就简单多了。

UI库的 table 的二次封装
  不用自定义列:
  <my-table-json :data="data" :colInfo="colInfo">
  </my-table-json>
  使用自定义列:
  <my-table-json :data="data" :colInfo="colInfo">
    <template #td_url="{ row }">
      <a :href="row.url" target="blank">{{row.name}}</a>
    </template>
    <template #td_mobile="scope">
      手机:{{scope.row.mobile}}
    </template>
  </my-table-json>

不需要自定义列的话,代码可以更简洁;需要自定义列的话,也支持用插槽的方式实现。

import myTableJson from'./comp/my-table-json.vue'
  import meta from'./grid.json'
  const colInfo = reactive(meta.itemMeta)
  const data = reactive([
    {
      name: '阿蒙',
      age: 18,
      mobile: '1399999991',
      url: 'https://naturefw.gitee.io/nf-rollup-ui-controller'
    },
    {
      name: '小李',
      age: 18,
      mobile: '1399999992',
      url: 'https://naturefw.gitee.io/nf-rollup-ui-controller/meta-base'
    },
    {
      name: '路飞',
      age: 18,
      mobile: '1399999993',
      url: 'https://naturefw.gitee.io/nf-rollup-ui-controller/meta-base'
    }
  ])

这样就不用手撸 el-table-column 了,交给子组件即可,同时还可以满足自定义列的需求。

是不是即简洁又灵活。这个彩蛋还满意吧。

看看效果:

128.png

json渲染 + 作用域插槽

在线演示

https://naturefw.gitee.io/nf-rollup-ui-controller/test-slot

源码

https://gitee.com/naturefw/nf-rollup-ui-controller/tree/master/src/views/test/slot

本文作者:自然框架

个人网址:jyk.cnblogs.com

声明:本文为 脚本之家专栏作者 投稿,未经允许请勿转载。

相关文章
|
2月前
|
JavaScript
vue组件中的插槽
本文介绍了Vue中组件的插槽使用,包括单个插槽和多个具名插槽的定义及在父组件中的使用方法,展示了如何通过插槽将父组件的内容插入到子组件的指定位置。
|
5天前
|
人工智能 API Apache
推荐3款开源、美观且免费的WinForm UI控件库
推荐3款开源、美观且免费的WinForm UI控件库
|
18天前
|
JavaScript
如何在 Vue 中使用具名插槽
【10月更文挑战第25天】通过使用具名插槽,你可以更好地组织和定制组件的模板结构,使组件更具灵活性和可复用性。同时,具名插槽也有助于提高代码的可读性和可维护性。
15 2
|
18天前
|
JavaScript
Vue 中的插槽
【10月更文挑战第25天】插槽的使用可以大大提高组件的复用性和灵活性,使你能够根据具体需求在组件中插入不同的内容,同时保持组件的结构和样式的一致性。
15 2
|
5天前
|
API C# 开发者
基于Material Design风格开源、免费的WinForms UI控件库
基于Material Design风格开源、免费的WinForms UI控件库!
|
2月前
|
JavaScript
Ant Design Vue UI框架的基础使用,及通用后台管理模板的小demo【简单】
这篇文章介绍了如何使用Ant Design Vue UI框架创建一个简单的后台管理模板,包括创建Vue项目、安装和使用ant-design-vue、以及编写后台管理通用页面的代码和样式。
Ant Design Vue UI框架的基础使用,及通用后台管理模板的小demo【简单】
|
1月前
|
JavaScript 搜索推荐
Vue 插槽全攻略:重塑组件灵活性
【10月更文挑战第7天】 Vue 的插槽(Slots)是一个强大的特性,用于增强组件的灵活性和可扩展性。插槽允许父组件向子组件传递内容,实现组件的复用和个性化展示。主要包括默认插槽、具名插槽和作用域插槽三种类型,分别适用于不同场景。通过插槽,可以提高组件的复用性、实现灵活的布局,并促进团队协作。
22 1
|
1月前
|
JavaScript 前端开发
VUE学习三:双向绑定指令(v-mode)、组件化开发(全局组件/局部组卷/组件通信)、组件化高级(slot插槽使用)
这篇文章是关于Vue.js框架中的v-model指令和组件化开发的详细教程,涵盖了从基础使用到高级功能的多个方面。
30 1
|
1月前
|
JavaScript 索引
Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案
Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案
110 0
|
2月前
|
JavaScript 前端开发 API
Vue2与Vue3插槽使用的区别及案例
Vue 3在插槽功能上的改进,体现了其对开发体验的持续优化。通过简化API、加强动态特性和提升性能,Vue 3使得插槽的使用更加灵活和高效。这些改进不仅有助于减轻开发者的负担,还为组件之间的高级交互和内容复用打开了新的可能性。随着Vue生态系统的不断成熟,我们有理由相信,Vue将继续为前端开发提供强大且易用的工具。
62 3