VUE2 大数据量(10w以内)的组件选取之路
一、本文所用的开源组件有
- vue-easy-table
- element的table
- vue-virtual-scroll的基础组件 --- RecycleScroller
二、问题背景
部门的运维平台最近在做一个redis白屏化的需求,就是把redis的命令操作转化为图形化界面操作。在这个需求有一个点,我觉得对于我这么一个新人来说,还是很有挑战性的,那就是如何将大量的key值(在研发环境阶段最大的数据量有73000+)数据展示到页面上,而不会影响到后续的操作流畅度。
遇到这个问题,第一时间,我就想到了数据懒加载,毕竟也都是背过八股文的,但是一查,就发现了新的问题。
问题1
懒加载大家随便去查一下,已经被社区讲透了。简单来说就是如下几步
- 设置一个list接受初始的data(getdata)
- 每次滑动到当前table的最底部,再次调用getdata
- 将新获得的data加入到list的后面
- 重复2、3两步,直到调用getdata无数据返回
这样有几个问题
- - list绑定的数据量越来越多,如果list中是普通的对象还好,如果是嵌套很深的对象,VUE绑定的响应式数据太多,会变得卡顿
- table组件渲染的行item越来越多,导致最终table组件会变得很卡
resolve 问题1的两个子问题
对于上面的子问题一,目前我还没有想到特别好的办法,或者编码技巧去解决,欢迎大家说出自己的想法。但是比较庆幸的是,我接受的后端数据结构比较简单,大致为如下
ts
{total:number,keys:Array<string>}
在后续使用 vue-virtual-scroll 或者 vue-easy-table 的过程中,我在加载到5w条的数据情况下,页面没有很明显的交互卡顿
对于子问题二,则可以推出我们今天的主角---虚拟滑动组件
本篇主要是记录实战中遇到的一些问题和选择,具体原理可以自行搜索
element ui下的table官方上好像大致可以支持2000个数据的展示,不会很卡,并且由于我们的项目使用的版本是2,比较老,所以没有懒加载,加载数据量大了之后,基本就卡得铛铛的,gif就不展示了,大家自行脑补一下。
第一次的选择 vue-easy-table
通过搜索发现vue-easy-table这个成熟的虚拟滑动 table 组件,使用较为方便和简单,但是有一个问题,就是在快速拖动的时候会出现大面积的空白,甚至有时间会直接卡住,不再渲染出里面的每一行,调试了很久,也问了一下github的作者,可惜还没有回复,如果有大佬知道,可以评论区教一下,这个组件还真的是开箱可用的,比较好的。 下面是我简单的代码展示
js
// 组件
<template>
<ve-table
:columns="columns"
:table-data="tableData"
:max-height="500"
:virtual-scroll-option="virtualScrollOption"
row-key-field-name="id"
ref="veTable"
/>
</template>
<script>
import data from "../assets/bigDataNew.js";
const { data: list } = data;
export default {
data() {
return {
virtualScrollOption: { enable: true, scrolling: this.scrolling },
columns: [
{ field: "id", key: "a", title: "id", align: "center", width: "20%" },
{ field: "pid", key: "b", title: "pid", align: "left", width: "20% " },
{
field: "name",
key: "c",
title: "address",
align: "right",
width: "60%",
},
],
tableData: list
};
},
methods: {
scrolling() {
// 滚动的时候可以干点啥
}
},
};
</script>
// assets/bigDataNew.js 是上面组件的数据源
const newdata = new Array(10000).fill(0).map((item, index) => {
return {
id: 'id' + index,
pid: 'pid' + index,
name: 'name' + index
}
})
export default {
"status": 200,
"statusCode": "获取成功",
"data": newdata
}
下面是出现空白的实际效果

并且这个空白时间的长短是随着数据量的增加而变长的,这个如果有大佬懂的话,希望可以在评论区讨论一下,学习一下
第二次 element table搭配分页
这种也可以解决dom渲染过多导致的页面卡顿问题,可以作为一种最后的备选方案
第三次 vue-vitrual-scroll(下面简称vvs) 模仿一个table
下面是我自己的方案,利用vvs的 RecycleScroller 组件,还有一个 DynamicScroller 组件(用于你不知道你table中的每一行高度时使用,他会自适应每一行的高度,但是会有一些问题,后面展示)
下面是vue2的代码,为每一行的item添加了el-row,并且添加了header,使得组件做出来的效果与页面其他的element的table风格大致相似
html结构解析
其html结构大致为header和RecycleScroller这两个部分,其中RecycleScroller中的插槽内容为一个el-row,el-row中又有两个插槽位,分别用于展示关键值key,并可以对key进行操作;另一个是操作插槽位,用于存放一些操作类dom
我没有用v-slot的原因是,我的vue版本是2.5,所以要用slot-scope,这个是隐形的坑啊,大家一定要看清自己的版本,官网的demo是对应2.6之后的了,我在这里浪费了好久的时间。一个周五晚上直接啥都没有干,就在社区里旷荡了
v2.cn.vuejs.org/v2/guide/co... 上面是vue2官网链接,大家可以看一下,我的哭晕在厕所里了
html
<template>
<div class="wrapper" style="font-size: 12px; width: 100%">
<el-row
style="padding: 12px 0"
type="flex"
justify="center"
ref="header"
class="header"
>
<el-col :span="7" class="header-col"
><div>id</div></el-col
>
<el-col :span="10" class="header-col"
><div>key</div></el-col
>
<el-col :span="7" class="header-col"
><div>操作</div></el-col
>
</el-row>
<RecycleScroller
style="color: #63656e;flex: 1;height: 0;"
:items="items"
:item-size="itemHeight"
key-field="key"
ref="scrollerRef"
@scroll-end="update"
:emit-update="true"
>
<template slot-scope="{ item, index }">
<el-row
:style="{
lineHeight: `${itemHeight}px`
}"
type="flex"
:class="[activeKey === item.key ? 'actice-row' : '',textVt]"
justify="center"
>
<el-col :span="7"
><div>{{ index + 1 }}</div></el-col
>
<el-col :span="10">
<el-tooltip
effect="dark"
:content="item.key"
placement="top"
>
<div class="escli">
<slot name="click" :data="{ item }"></slot>
</div>
</el-tooltip>
</el-col>
<el-col :span="7">
<slot name="operations" :data="{ index, item }"></slot>
</el-col>
</el-row>
</template>
</RecycleScroller>
</div>
</template>
js解析
props
- items为数据源,用于展示虚拟滚动的全部数据
- itemHeight为每一行的高度,RecycleScroller 必须设置,不设置的话,就直接报错
- activeKey为判断当前行是否是被激活的状态,利用这个prop可以做选中高亮
update 函数搭配上面代码中的@scroll-end,即当滚动到list的底部时,会触发emit,我是通过emit弹出一个子事件,让父组件捕捉,从而开始懒加载数据
js
//子组件
<script>
export default {
props: [
'items',
'itemHeight',
'activeKey'
],
data: function () {
return {
textVt: 'text'
}
},
methods: {
update () {
this.$emit('getlist') // 让父组件得知已经滑动到底部了需要添加数据
}
}
}
</script>
// 父组件捕捉的代码 *不属于这上中下三部分的代码哈*
// 当vvs滑动到底部emit出的事件
getlist() {
// 懒加载数据
this.getKeys():
},
css解析
按照element的样式给各行配置一下样式,唯一需要注意的是,由于 RecycleScroller 组件使用了transform这个css属性来完成虚拟滑动的item渲染,所以会存在下面的item覆盖很少很少一部分上面item的情况 所以text-vt要设置的是border-top有一条线,如果设置为border-bottom的话,会被覆盖。
css
<style scoped>
.wrapper{
display: flex;
flex-direction: column;
height: 100%;
}
.header {
background-color: #f4f5f7;
}
.header-col {
border-right: 2px solid #fff;
}
.elli {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.text {
border-top: 1px solid #ebeef5;
}
.text:hover {
background-color: #f5f7fa;
cursor: pointer;
}
.actice-row{
background-color: #ecf5ff !important;
}
</style>
下面是效果演示
基本上没有什么硬伤了

important
当然还一个重要的点,那就是 RecycleScroller 组件需要一个固定的height,否则报错,这里我尝试了两种方法
-
第一种,直接style给定一个height,这种方式,很快就被现实否掉了,原因很简单,不是响应式的
-
第二种,也是我现在采用的一种,但是比较繁琐,那就是利用flex布局,加上flex-direction设置为column,让上下呈现一种类似于瀑布流的样式,这种需要一层一层的明确当前父级dom的height是不是固定的 ,而且子一级的dom,一般是两个,第一个dom的高度,可以由其子元素来控制,即不设置height属性;第二个dom的height设置为0,flex为1,获取剩余的高度。
-
其实上面第二种方式,我是偶然间发现的,height设置为0,如果flex为1的话,height的高度就默认为父级组件剩余的高度了,有没有大佬讲讲这个是css机制吗?(求助,最好有链接能讲讲)
最后来简单聊一下 vvs 的 DynamicScroller组件
这个组件设置的目的就是兼容一些你自己不知道自己行高为多少的情况,但是我发现有一些问题,如果使用这个组件的话,在拖动的时候,也会发生一些抖动,这里就不贴图了,我感觉原理就是在渲染不同的行高时,突然增加的行高,可能会影响每一行的item渲染,具体这一块没有深究。
三、总结
vue-virtual-scroll在10w以内的数据量下,表现比较好。当然如果后面vue-easy-table能让那个滑动块快速拖动白屏的问题有更好一点的效果的话,我觉得vue-easy-table是我觉得目前最好的选择。 最后本人在这次开发中,有两个疑惑(两个斜体的任务列表),如果有大佬愿意指点的话,非常感谢!!!