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>
  • 看看效果

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

那么插槽在实际项目里可以有哪些作用呢?我们可以参考一下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 限定具名插槽内容的范围,我们来看看效果:

作用域插槽

插槽是父组件的,不是子组件的,父组件可以完全操作插槽里的组件。

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

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

还是看看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 了,交给子组件即可,同时还可以满足自定义列的需求。

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

看看效果:

在线演示

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

源码

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

通过UI库深入了解Vue的插槽的使用技巧的更多相关文章

  1. vue常见前端UI库

    vue常见前端UI库 VUE资源汇总:https://github.com/RuMengkai/awesome-vue VUX 项目主页:https://vux.li/#/ github地址:http ...

  2. ElementUI(vue UI库)、iView(vue UI库)、ant design(react UI库)中组件的区别

    ElementUI(vue UI库).iView(vue UI库).ant design(react UI库)中组件的区别: 事项 ElementUI iView ant design 全局加载进度条 ...

  3. vue实现选项卡切换--不用ui库

    vue的ui库中基本都有选项卡切换的组件,但是在项目开发过程中却不一定能很好的为我们所用,因为里面的样式和 一些状态并不能很好的根据我们的项目需求进行定制.最近项目中使用的是vant-ui中的标签页, ...

  4. vue中使用第三方UI库的移动端rem适配方案

    需求:使用vue-cli脚手架搭建项目,并且使用第三方的UI库(比如vant,mint ui)的时候,因为第三方库用的都是用px单位,无法使用rem适配不同设备的屏幕. 解决办法:使用px2rem-l ...

  5. 【摸鱼神器】UI库秒变LowCode工具——列表篇(一)设计与实现

    内容摘要: 需求分析 定义 interface 定义 json 文件 定义列表控件的 props 基于 el-table 封装,实现依赖 json 渲染 实现内置功能:选择行(单选.多选),格式化.锁 ...

  6. 【摸鱼神器】UI库秒变低代码工具——表单篇(一)设计

    前面说了列表的低代码化的方法,本篇介绍一下表单的低代码化. 内容摘要 需求分析. 定义 interface. 定义表单控件的 props. 定义 json 文件. 基于 el-form 封装,实现依赖 ...

  7. 【摸鱼神器】UI库秒变低代码工具——表单篇(二)子控件

    上一篇介绍了表单控件,这一篇介绍一下表单里面的各种子控件的封装方式. 主要内容 需求分析 子控件的分类 子控件属性的分类 定义 interface. 定义子控件的的 props. 定义 json 文件 ...

  8. 移动Web UI库(H5框架)

    1.Framework7 Framework7 - is a free and open source mobile HTML framework to develop hybrid mobile a ...

  9. 移动Web UI库(H5框架)有哪些,看这里就够了

    前言 今年上半年,项目组在项目开发的过程中建立了一套风格统一,组件丰富完善,命名统一规范的PC端UI库,适用于做大型站点,该UI库也是应用到了整个平台的项目中,在各个项目组中进行推广.因为项目的保密性 ...

随机推荐

  1. CF1003C Intense Heat 题解

    Content 给定一个长度为 \(n\) 的数列,求数列中所有长度 \(\geqslant k\) 的区间的最大平均值. 数据范围:\(1\leqslant k,n,a_i\leqslant 500 ...

  2. CF111A Petya and Inequiations 题解

    Content 请找出一个由 \(n\) 个正整数组成的数列 \(\{a_1,a_2,\dots,a_n\}\),满足以下两种条件: \(\sum\limits_{i=1}^na_i^2\geqsla ...

  3. XMLHttpRequest() 如何预检查CORS跨域请求的?

    https://blog.csdn.net/qq_38261174/article/details/90691058

  4. [WPF] 实现 WPF 的 Inner Shadow

    在 WPF 中,我们通常用 DropShadow 做阴影效果,但都是做外阴影.内阴影(Inner Shadow)的话其实也不是不可以,就是有些曲折.这篇文章介绍几种做内引用的做法. 文章涉及到以下概念 ...

  5. Linux生成SSH密钥对

    执行 ssh-keygen -t rsa -P "" -f "/root/.ssh/id_rsa" 进入 cd /root/.ssh目录 (这里的root 是因 ...

  6. UE4之第一个飞机游戏

    开始之前 UE4官网 初识ue4教程(1~9节): https://www.bilibili.com/video/BV164411Y732?p=1 第一个飞机游戏: http://www.sikied ...

  7. 网络编程之UDP中一个包的大小最大能多大

    读书笔记:here 结论1:局域网环境下,建议将UDP数据控制在1472字节以下 一定要知道 因为链路层的传输单元(MTU)是1500字节,1500字节中并不包含链路层的首尾18个字节.1500字节是 ...

  8. c++11之all_of 、 any_of 和 none_of 的用法

    0.时刻提醒自己 Note: vector的释放 1.区别 函数 功能 all_of 区间[开始, 结束)中是否所有的元素都满足判断式p,所有的元素都满足条件返回true,否则返回false. any ...

  9. 【LeetCode】778. Swim in Rising Water 水位上升的泳池中游泳(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址: https://leetcode.com/problems/swim-in- ...

  10. 1036 - A Refining Company

    1036 - A Refining Company   PDF (English) Statistics Forum Time Limit: 3 second(s) Memory Limit: 32 ...