午后写的这篇文章,微风加阳光,非常惬意🏖。
而后,话很少说,咱们直接进入正题,今天主要会讲解一下可伸缩列、固定列、多级表头和几个表格的常见问题✍️,干货满满哦😯。api
舒适提示:本篇文章是继上次 table 组件了解一下? 这篇文章以后写的,因此建议先去阅读一下前面那篇文章👀。固然也能够直接往下看,由于这里主要说下思路,没放多少代码😁。数组
可伸缩列,顾名思义就是咱们能够经过拖动表头的 border
来实现列宽的大小,看看下面这张图你就懂了👇: bash
咱们在表头的每一个 th
里面都多增长一个 div
,也就是上图中红色的部分,而后绝对定位于 th
的右边便可。数据结构
在表头 header
和表体 body
同级的地方增长一个 div
来表示上图中的虚线,默认是隐藏的,就像下面这张图: app
对红色部分的鼠标事件进行监听:当鼠标按下的时候,就显示虚线,并实时改变虚线的 left
值,当鼠标抬起的时候就隐藏虚线,并计算出拖拽后的列宽,以后修改的 columns
里面对应列的 width
便可,这样表头和表体的列宽都会同步改变。 固然,还要记得在鼠标抬起时解绑 mousemove
和 mouseup
事件,这是个好习惯。
以上就是可伸缩列的实现方式。dom
接下来咱们来看看固定列是怎么实现的。首先仍是 api 的设计,这个应该容易想到,咱们在 columns
里面把须要固定的列添加上 fixed
属性便可,它的值有两种选择(left
或 right
),就像下面这样:函数
columns: [
{
title: '姓名',
key: 'name',
width: 100,
fixed: 'left'
}
...
]
复制代码
为了简化问题,这里咱们只考虑左列固定,由于右列固定也是同样的。
这里先一句话说明下原理😁:就是多渲染一份表格并绝对定位在原来的表格之上,下面这张图应该能帮助你理解👇: post
由于要把须要固定的列放到左边,因此咱们一开始最须要处理的就是表格数据,把全部有 fixed="left"
属性的列排在前面。大数据
再来就是正常渲染两个表格(A 和 B),事实上表格 A 和表格 B 是如出一辙的,只不过表格 B多设置了一些属性,好比绝对定位在左上角、定宽(宽度就是有fixed="left"
属性的列的宽度之和)、溢出隐藏啊等。具体代码结构以下图所示: this
fixed
部分咱们能够设置
visibility: hidden
,由于它不须要展现,并且 Element 也是这样写的;一样地,对于表格 B 的非
fixed
部分也能够设置
visibility: hidden
。
visibility: hidden
而不设置成 display: none
呢?display: none
难道不是能够渲染更少的 dom 吗?设置成 visibility
的意义在哪里?这是个不错的问题,建议你们思考一下下。。。再往下看😁。
带着疑问我顺便看了下 iView 和 Ant Design 的 dom 结构,发现 iView 也是用 visibility: hidden
来处理的,而 Ant Design 则是直接不渲染了,这就很奇怪啦!因而乎我把 iView 和 Element 的 visibility: hidden
换成 display: none
试了一下,发现好像也 ok,表格展现也是对的,没什么问题,因此是为何呢?其实最主要的缘由是把 visibility: hidden
换成 display: none
会引起行对不齐的问题。
什么意思呢?就是说若是咱们设置 display: none
的话,表格 A 里面的行高是不固定的,但这时候表格 B 是没有展现表格 A 中表体的内容,因此表格 B 不能同步表格 A 里面的高;而若是咱们设置成 visibility: hidden
的话,表格 A 和表格 B 实际上是都包含全部数据的,只是视觉上不可见而已,这样它们的行高就可以保持彻底一致,虽然会致使多余的 dom 元素。
那 Ant Design 为何能够呢?其实 Ant Design 也是有这个问题的,虽然它没有渲染多余的 dom,可是它会事先计算出表格 A 的行高,而后在去同步设置固定列的行高,此外在表格大小、列宽变化的时候也要去同步。它们是两种不一样的方案,你们本身好好体会一下🙌。
固然,这里咱们采用的是 Element 和 iView 的方案。
上面的实现方式会有什么问题呢🤔?最明显的问题就是表格 A 和表格 B 是割裂的,因此滚动其中一个表格的时候,另一个表格是不会跟着响应的。事实上每一个表格里面的表头和表体也是割裂的,因此如今咱们要作就是同步滚动:当咱们横向滚动表格 A 的表体时,咱们须要同步滚动表格 A 的表头部分;当咱们纵向滚动表格 A 的表体时,咱们须要同步滚动表格 B 的表体部分。
这里以表格 A 里面的表体(A__body
)滚动为例子,简要说下具体作法: A__body
横向滚动时:获取 A__body
的 scrollLeft
值,而后把值同步到表格 A 的表头; A__body
纵向滚动时:获取 A__body
的 scrollTop
值,而后把值同步到表格 B 的表体。 固然滚动是一个高频动做,因此咱们能够进行防抖处理;还能够尝试用 transform
来替代 scrollTop
和 scrollLeft
进行滚动。
同第三步同样,当咱们 hover
每一行的时候也须要像滚动同样进行同步,也就是 hover
样式的同步。
一样地咱们以 hover
表格 A 的表体为例🌰,监听行的 mouseenter
和 mouseleave
事件,当鼠标移入到某一行,这时候你是能获取该行信息的,而后同步到固定列的对应行便可;同理 hover
到固定列的时候也要同步到表格 A,这里就再也不赘述。
首先仍是 api 的设计,咱们但愿在 columns
里面加个 children
就能实现,就像下面这样(顺便看下多级表头长什么样):
columns: [
{
title: '日期',
key: 'date',
width: 200
},
{
title: '配送信息',
children: [
{
title: '姓名',
key: 'name',
width: 200
},
{
title: '地址',
key: 'addr'
}
]
}
]
复制代码
v-for
循环,让咱们截个 iView 的代码看看大致结构:
columns
进行格式化,使其变成 下面这个样子(二维数组):
level
、
rowspan
和
colspan
,后面两个是用来实现合并单元格的,好比
columns
的第一项日期,它的
colspan
应该是它
children
的总数,若是没有
children
就是1;它的
rowspan
应该等于最大层数-当前层数+1,若是有
children
则为1。
表体渲染就简单了,只不过咱们一样要对 columns
进行一下处理,咱们先看看整理以后的列大概长什么样:
columns
就是遍历一下旧的
columns
,有
children
就继续遍历获取子列,没
children
就直接取出该列,相似于获取到全部叶子节点的列,这个新的列会代替原有的
columns
来渲染,因为这个数据结构转换简单点,因此我把代码贴了上来:
const getAllColumns = (cols, forTableHead = false) => {
const columns = deepCopy(cols);
const result = [];
columns.forEach((column) => {
if (column.children) {
if (forTableHead) result.push(column);
result.push.apply(result, getAllColumns(column.children, forTableHead));
} else {
result.push(column);
}
});
return result;
};
复制代码
其实仍是个数据转换的问题,你们能够看下下面两张图加深理解👇:
事实上咱们的表格和表体的列宽其实仍是不一致的,好比当表体有滚动条的时候,表头没有,因此就会出现一个滚动条宽度的差距,这时候一般的作法是当纵向滚动条存在的时就在表头末尾加上一列去弥补这个宽度差,通常咱们称之为 gutter
。那这个 gutter
的宽度值是多少呢?其实就是滚动条的宽度,那如何计算滚动条的宽度呢?大概原理就是建立一个 div
,而后用(无 overflow: scroll
属性的 div
宽度)-(有 overflow: scroll
的 div
宽度),就是滚动条的宽度。
实际开发中,用 Element 的时候若是表格不知道为啥就错位了,能够尝试用如下方法解决:
this.$nextTick(() => {
this.$refs['table'] && this.$refs['table'].doLayout()
})
复制代码
表格里若是满屏都是 tooltip
和 input
是会卡顿的,毕竟事件监听和 dom 结构都变多了,因此要避免这种状况的发生。好比行是编辑状态的时候才显示 input
;tooltip
改为实时渲染,不要一开始把每一个都加上 tooltip
。
v-for
的时候不要用 index
来绑定 key
值,由于 index
多是会改变的,因此并不可靠,尽可能用 id
来绑定。
对于成千上万行的数据渲染,若是发生了卡顿是很正常的,这时候若是你的表格数据只是用来纯展现的话能够在组件中加上 functional: true
使其成为函数化组件,这样能减小渲染开销。另外一种就是虚拟列表了,虽然说表格数据成千上万,但可视区的数据就那么多、范围就那么大,咱们永远只须要渲染可视区域和可视区上下附近的一部分数据便可,其余不用渲染,而后在滚动的时候根据滚动的多少来计算出当前要显示的数据区间便可,大致思路就是这个样子,固然了,It's easier said than done 😂。
吼吼,至此,咱们终于把 table 组件的经常使用功能点讲完了,说实话,是真的麻烦,不过但愿可以对你有帮助,嘻嘻😄!