table 组件了解两下?

前言

午后写的这篇文章,微风加阳光,非常惬意🏖。
而后,话很少说,咱们直接进入正题,今天主要会讲解一下可伸缩列、固定列、多级表头和几个表格的常见问题✍️,干货满满哦😯。api

舒适提示:本篇文章是继上次 table 组件了解一下? 这篇文章以后写的,因此建议先去阅读一下前面那篇文章👀。固然也能够直接往下看,由于这里主要说下思路,没放多少代码😁。数组

可伸缩列

可伸缩列,顾名思义就是咱们能够经过拖动表头的 border 来实现列宽的大小,看看下面这张图你就懂了👇: bash

上图的意思应该表达的挺明了,如今咱们简要看下具体实现步骤👇:

第一步:

咱们在表头的每一个 th 里面都多增长一个 div,也就是上图中红色的部分,而后绝对定位于 th 的右边便可。数据结构

第二步:

在表头 header 和表体 body 同级的地方增长一个 div 来表示上图中的虚线,默认是隐藏的,就像下面这张图: app

第三步:

对红色部分的鼠标事件进行监听:当鼠标按下的时候,就显示虚线,并实时改变虚线的 left 值,当鼠标抬起的时候就隐藏虚线,并计算出拖拽后的列宽,以后修改的 columns 里面对应列的 width 便可,这样表头和表体的列宽都会同步改变。 固然,还要记得在鼠标抬起时解绑 mousemovemouseup 事件,这是个好习惯。
以上就是可伸缩列的实现方式。dom

固定列

接下来咱们来看看固定列是怎么实现的。首先仍是 api 的设计,这个应该容易想到,咱们在 columns 里面把须要固定的列添加上 fixed 属性便可,它的值有两种选择(leftright),就像下面这样:函数

columns: [
    {
      title: '姓名',
      key: 'name',
      width: 100,
      fixed: 'left'
    }
    ...
]
复制代码

为了简化问题,这里咱们只考虑左列固定,由于右列固定也是同样的。
这里先一句话说明下原理😁:就是多渲染一份表格并绝对定位在原来的表格之上,下面这张图应该能帮助你理解👇: post

其实上图已是实现的核心了,So,咱们就直接说下具体实现方式😎:

第一步:处理表格数据

由于要把须要固定的列放到左边,因此咱们一开始最须要处理的就是表格数据,把全部有 fixed="left" 属性的列排在前面。大数据

第二步:渲染两个表格

再来就是正常渲染两个表格(A 和 B),事实上表格 A 和表格 B 是如出一辙的,只不过表格 B多设置了一些属性,好比绝对定位在左上角、定宽(宽度就是有fixed="left"属性的列的宽度之和)、溢出隐藏啊等。具体代码结构以下图所示: this

此外,对于表格 A 的 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__bodyscrollLeft 值,而后把值同步到表格 A 的表头; A__body 纵向滚动时:获取 A__bodyscrollTop 值,而后把值同步到表格 B 的表体。 固然滚动是一个高频动做,因此咱们能够进行防抖处理;还能够尝试用 transform 来替代 scrollTopscrollLeft 进行滚动。

第四步:同步hover

同第三步同样,当咱们 hover 每一行的时候也须要像滚动同样进行同步,也就是 hover 样式的同步。
一样地咱们以 hover 表格 A 的表体为例🌰,监听行的 mouseentermouseleave 事件,当鼠标移入到某一行,这时候你是能获取该行信息的,而后同步到固定列的对应行便可;同理 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 进行格式化,使其变成 下面这个样子(二维数组):
其中每一个数据节点都会给个 levelrowspancolspan,后面两个是用来实现合并单元格的,好比 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: scrolldiv 宽度),就是滚动条的宽度。

二、错位

实际开发中,用 Element 的时候若是表格不知道为啥就错位了,能够尝试用如下方法解决:

this.$nextTick(() => {
    this.$refs['table'] && this.$refs['table'].doLayout()
})
复制代码

三、卡顿

表格里若是满屏都是 tooltipinput 是会卡顿的,毕竟事件监听和 dom 结构都变多了,因此要避免这种状况的发生。好比行是编辑状态的时候才显示 inputtooltip 改为实时渲染,不要一开始把每一个都加上 tooltip

四、key 值

v-for 的时候不要用 index 来绑定 key 值,由于 index 多是会改变的,因此并不可靠,尽可能用 id 来绑定。

五、大数据渲染

对于成千上万行的数据渲染,若是发生了卡顿是很正常的,这时候若是你的表格数据只是用来纯展现的话能够在组件中加上 functional: true 使其成为函数化组件,这样能减小渲染开销。另外一种就是虚拟列表了,虽然说表格数据成千上万,但可视区的数据就那么多、范围就那么大,咱们永远只须要渲染可视区域和可视区上下附近的一部分数据便可,其余不用渲染,而后在滚动的时候根据滚动的多少来计算出当前要显示的数据区间便可,大致思路就是这个样子,固然了,It's easier said than done 😂。

小结

吼吼,至此,咱们终于把 table 组件的经常使用功能点讲完了,说实话,是真的麻烦,不过但愿可以对你有帮助,嘻嘻😄!

相关文章
相关标签/搜索