提出背景
公司组会上提到下拉列表元素过多的话会导致页面卡顿,而懒加载+分页的方案并不能阻止页面dom增多,所以就需要使用虚拟滚动方案。
我翻看掘金和csdn上的文章后发现很多是手写虚拟滚动方案,虽然手写虚拟滚动能够实现高度定制化并做到性能优于第三方库,但是开发效率也是不可忽视的一环。
引用第三方库虽然不利于极致的性能优化但是能大大提高开发效率(特别是这种复杂的功能)。
什么是虚拟滚动该如何解决
【全网最通俗易懂】虚拟列表1-(基础篇):渲染十万条数据不卡顿(附demo和源码)大家好,我是前端大卫。 今天想和大家分 - 掘金
这篇文章讲解的很清楚了
虚拟滚动有什么好处
- 减少
DOM
数量可以减少浏览器需要渲染和维持的DOM
元素数量,进而内存占用也随之减少,这使得浏览器可以更快地响应用户操作 - 浏览器的回流
reflow
和重绘repaint
操作通常是需要大量计算的,并且随着DOM
元素的增多而变得更加频繁和复杂,通过虚拟滚动个减少需要管理的DOM
数量。 - 更快的首屏渲染时间,超大列表的全量渲染很容易导致首屏渲染时间过长。
解决方案(elementUI、elementPlus)
vue2
虚拟滚动+自动分页
因为公司很多项目是vue2项目,所以这里使用标准vue2写法。
基于vue2的elementUI组件是不支持虚拟滚动的,所以需要我们自己实现。
我这里推荐的第三方库是:vue-virtual-scroll-list
,只需要npm i vue-virtual-scroll-list
下载即可。
我的测试版本:"element-ui": "^2.15.14"
"vue":"^2.6.14"
"vue-virtual-scroll-list":"^2.3.5"
首先我们要二次封装一下el-select
:
/components/virtual-select.vue
这里手写节流函数throttle
来控制分页触发频率,及无论用户滚动有多块都只保证规定时间内只触发一次分页。
javascript
<template>
<el-select v-model="value" clearable filterable>
<!--
:data-key="'value'" 指定数据唯一标识字段
:data-sources="data" 数据来源
:data-component="item" 列表项展示组件
:estimate-size="10" 自定义行高
:keeps="10" 保持渲染的DOM节点数量
-->
<virtual-list
class="list"
style="height: 250px; overflow-y: auto"
:data-key="'value'"
:data-sources="data"
:data-component="item"
:estimate-size="10"
:keeps="10"
/>
</el-select>
</template>
<script>
import VirtualList from "vue-virtual-scroll-list";
import ListItem from "./item.vue";
//手写节流函数
function throttle(fn, wait) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= wait) {
fn.apply(this, args)
lastTime = now
}
}
}
export default {
name: "VirtualSelect",
components: { VirtualList },
data() {
return {
options: [],
data: [],
value: "",
pageNo: 0,
item: ListItem,
};
},
// 组件挂载前获取数据
beforeMount() {
this.getList();
},
methods: {
// 模拟获取大数据(初始化时调用)
getList() {
const data = [];
for (let i = 0; i < 25000; i++) {
data.push({ label: "选择" + i, value: "选择" + i });
}
this.allData = data;
this.data = data;
this.getPageList();
},
// 获取分页数据(用于滚动加载)
getPageList(pageSize = 10) {
this.pageNo++;
const list = this.data.slice(0, pageSize * this.pageNo);
this.options = list;
},
// 修改后的加载方法
loadMore: throttle(function() {
this.getPageList();
}, 200) // 200ms节流间隔
},
};
</script>
<style>
*::-webkit-scrollbar {
display: none;
}
* {
scrollbar-width: none;
}
* {
-ms-overflow-style: none;
}
</style>
/components/item.vue
virtual-select.vue
组件中的最小item
元素
react
<template>
<el-option :label="source.label" :value="source.value"></el-option>
</template>
<script>
export default {
name: 'ListItem',
props: {
source: {
type: Object,
default() {
return {}
}
}
}
}
</script>
<style scoped>
</style>
在需求界面使用
react
<template>
<virtual-select v-model="selectedValue" :data="largeDataArray" />
</template>
<script>
import VirtualSelect from "../src/components/virtual-select.vue"; // 修改导入路径
export default {
components: {
VirtualSelect,
},
data() {
return {
selectedValue: "",
// 初始化测试数据
largeDataArray: Array.from({ length: 25000 }, (_, i) => ({
label: `选择${i}`,
value: `选择${i}`,
})),
};
},
};
</script>
<style></style>
成果展示:
可以看到我模拟了25000条数据进行测试,通过下图看出只渲染了10个item,这是由于我在virtual-select.vue
组件中设置的:keeps="10"
这个参数是保持渲染的DOM节点数量,这个数量越多,缓冲区的元素数量也就越多,所以这个参数可以多设置一些。
vue3
基于vue3的elementplus已经集成了虚拟列表功能:select-v2