如何优化elementUI的表格在大数据场景下的性能

4,604 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言:

elementUI 是vue2技术栈的常用ui组件,但是如果要在elementUI中渲染大量数据,会带来卡顿问题,原因是el-table组件会渲染所有数据成dom节点,过多的dom节点带来的性能开销导致卡顿。

为了解决这个问题,我们可以让表格只渲染当前可以看到的数据,其它数据等滚动到界面的时候再渲染。elementUI升级成Plus后对这个进行了优化,思路就是这样,一起来看下。

elementPlus的虚拟化表格:

elementPLUS已经正式发布了,其中有一点升级的是增加了两个虚拟化组件选择器和表格

  • Select V2 虚拟列表选择器
  • Virtualized Table 虚拟化表格b。

这两个组件的组件的更新为为了提升大数据场景下的性能。

引用官方文档的原话:

Virtualized Table 虚拟化表格beta#

在前端开发领域,表格一直都是一个高频出现的组件,尤其是在中后台和数据分析场景。 但是,对于 Table V1来说,当一屏里超过 1000 条数据记录时,就会出现卡顿等性能问题,体验不是很好。 通过虚拟化表格组件,超大数据渲染将不再是一个头疼的问题。

这个表格究竟有什么不同的,让我们打开f12看一下。

可以看到,我滚动到900多行,但是dom结构只渲染了少量数据。每一行的dom上都加了绝对定位,用于定位到当前滚动到的位置。

实现虚拟化表格的思路:

既然我们需要让滚动条滚动到下方时,只渲染当前被展示在视窗的几行数据,那么至少需要做到两点:

  • 计算出所有数据被真实渲染后的高度,然后用一个空的div元素来撑开容器的高度,让表格出现滚动条。
  • 根据滚动条滚动到的位置来计算出当前应该展示哪些数据。

先看第一点:

空的div元素来撑开容器的高度,让表格出现滚动条:

简单画一个图:

浅绿色是我们添加的空元素(虚拟容器)用来撑开表格body的,让滚动条显示。

那么接下来的问题就是算这个虚拟容器的高度。最简单的方法是如果每一行的高度是相同的,那么我们只需要用总数乘以一个固定的值就好了。

但是如果每行高度不一样呢?尤其是有的数据文字多会换行导致高度变高,那么计算出的高度就有问题了。

我的思路是,由于数据没有渲染成dom时我们并不知道这一行到底多高,所以我们先给每行一个固定的高度去算虚拟容器的高度,等渲染新数据的时候更新一下虚拟dom的高度。

这里只谈思路,代码实现的话可以参考文章末尾给的码云地址!

再看第二点:

根据滚动条滚动到的位置来计算出当前应该展示哪些数据:

我们拿到表格body的dom元素后,很容易可以通过scrollTop来得出被卷走的高度。

然后根据这个高度,和每行数据所占的高度来算出表格中应该渲染的第一条数据是哪一条。

接着我们可以根据表格body自身的高度和每行数据的高度去算出表格中可以看到多少条数据。

这样我们达到只渲染可视dom的目的。

现在只谈思路,代码实现参考下方项目。

注意点:

  • 虚拟容器和表格数据都是表格body的子容器,他们是重叠关系,请使用绝对定位position:absolute
  • 表格数据的偏移可以用transform属性,性能更好。

写一个无限滚动加载的组件包:

根据上述思路,我写了一个npm包,可以在项目中通过npm install infinite-scroll-table来安装并使用。

npm地址:infinite-scroll-table - npm (npmjs.com)

详细介绍如下:

无限滚动组件说明

这是一个针对 vue2 和 elementUI 的无限滚动组件,可实现虚拟加载。

概要

特点一 滚动加载

本组件针对 el-table,获取 el-table 中的 table_wraper 元素,并添加滚动监听事件。
滚动到底部时调用加载方法,添加更多数据到维护的数组(scrollData)中。

特点二 虚拟列表

本组件,初始化时根据传入的每行最小高度值(itemHeight)计算出当前列表最多能展示多少行数据。
之后每次滚动并不会渲染维护数组(scrollData)中的所有数据,而是根据当前滚动的距离来渲染出对应的数据。 这样的好处是节约 dom 开销,提升性能。

特点三 不定高的列表

vue 的表格中每一行数据可能存在换行导致高度不一致,也就是每一行的高度是不定高的。这里组件仍然可以正确渲染。

注意

本组件只针对使用 vue2 和 elementUI 的项目

安装

npm install infinite-scroll-table

使用方法

在 main.js 中引入

import InfiniteScrollTable from "infinite-scroll-table";
Vue.use(InfiniteScrollTable);

在组件中使用
InfiniteScrollTable 组件包裹 el-table 组件,然后

传入三个配置:

  • 列表维护数据的总数组:scrollData
  • 加载更多数据的方法:getMoreData
  • 滚动的相关配置:loadConfig

侦听一个事件:

  • 侦听 setTableData 事件,该事件会返回一个数组,表示当前滚动距离应该显示的数据。

加载方法说明:

getMoreData 对应加载更多数据的方法。这个方法一般是 ajax 请求,也就是异步的方法。
为了组件控制加载的过程,我们需要在请求前,调用组件的loadStart方法,
请求结束后调用组件的loadEnd方法。

如下代码中使用 ref 标记了组件,加载前后分别调用了this.$refs.ref_infinite_table.loadStart()this.$refs.ref_infinite_table.loadEnd()

并且加载结束后注意设置 loadConfig 中的 totalCount 的值,以便于组件判断已经加载完了。

详细介绍,参见下方配置详解。

完整示例:

<template>
    <InfiniteScrollTable
        ref="ref_infinite_table"
        :scrollData="scrollData"
        :getMoreData="getMoreDataFn"
        :loadConfig="loadConfig"
        @setTableData="setTableDataFn"
    >
        <el-table :data="tableData" style="width: 100%" height="350">
            <el-table-column prop="id" label="编号"></el-table-column>
            <el-table-column prop="name" label="姓名"></el-table-column>
        </el-table>
    </InfiniteScrollTable>
</template>
<script>
export default {
    data() {
        return {
            loadConfig: {
                totalCount: 0,
            },
            scrollData: [],
            tableData: [],
        };
    },
    methods: {
        async getMoreDataFn() {
            this.$refs.ref_infinite_table.loadStart(); // 加载前调用 loadStart
            await this.queryData(); // queryData 是你请求后端数据的方法
            this.loadConfig.totalCount = 99; // 注意设置后端共有多少数据,以便判断结束
            this.$refs.ref_infinite_table.loadEnd(); // 加载后调用 loadEnd
        },
        setTableDataFn(arr) {
            this.tableData = arr;
        },
        async queryData() {
            await new Promise((resolve) => {
                setTimeout(() => {
                    resolve();
                }, 1000);
            });
            let len = this.scrollData.length;
            for (let i = 0; i < 10; i++) {
                this.scrollData.push({ id: len + i, name: "zyl" });
            }
        },
    },
};
</script>

配置详解

loadConfig

示例:

loadConfig: {
    loadingText: '玩命加载中', // 加载时提示文字
    overText: '你已经碰到我的底线了(没有更多数据)', // 所有数据加载完毕后显示文字
    totalCount: 0, // 传totalCount 或者 isOver  ========== 重要
    isOver: false, // 传isOver或者 totalCount  ========== 重要
    itemHeight: 40, // 每一行数据的最小高度,不传默认40
    distance: 40 // 滚动条距离底部并触发滚动加载的距离 不传 则默认为20
}

setTableData

为了节约 dom 的开销,我们尽量只渲染当前容器你能看到的数据。而el-table组件绑定的 data 你传入多少数据,就会显示多少数据。
所以我们把所有的数据放在 scrollData 中,通过 setTableData 事件传递出当前应该显示的数据给 tableData。
所以拿到 setTableData 传出的数据,直接赋值给 el-table 绑定的 data 就好了。
示例:

setTableDataFn(arr) {
    this.tableData = arr; // tableData 是el-table绑定的data
},

组件可被调用的几个方法

  • infiniteLoadStart 加载数据前调用 ========== 重要
  • infiniteLoadEnd 加载数据结束时调用 ========== 重要
  • scrollToTop 滚动到顶部
  • scrollToNextPage 滚动到下一页
  • scrollToPrePage 滚动到上一页
  • updateTableSize 传入的元素尺寸变化时调用
    使用$refs['这个组件的 ref 名称'].fn() 来调用,fn 对应上述一个方法。

其它传参

  • showTopBtn 是否展示回到顶部的按钮(滚动到下方才会展示)
  • showBottomBar 是否展示下方表示加载状态的横条
  • loadImmediate 表格数据不足以滚动时 是否立刻加载更多数据

注意点

isOver 或 totalCount 只用一种来控制是否结束

其它

本组件还支持一种便捷用法,在el-table上直接使用v-tableScrollLoad指令来让表格可以滚动加载。这个指令绑定一个加载数据的方法。
使用实例:

// loadingFn是加载数据的方法
<el-table v-tableScrollLoad="loadingFn" :data="tableData" stripe height="350">
    <el-table-column label="姓名" prop="name"></el-table-column>
    <el-table-column label="账户号" prop="acNo"></el-table-column>
    <el-table-column label="操作"></el-table-column>
</el-table>

注意点:

  • 初始的 tableData 如果为 0,无法触发滚动,所以第一次查询需要写个额外的方法
  • 为了避免重复加载,应该自行在 loadingFn 方法中判断是否正在查询,是否所有加载结束.
  • 此方法默认触发滚动距离是 20,可通过给v-tableScrollLoad指定传参来更改,如:v-tableScrollLoad[40]="loadingFn"

码云地址:git@gitee.com:zyl-ll/infinite-scroll-table.git