解决el-select数据量过大的3种方法
在准备上线的后台管理系统中,我们发现有两个下拉框(select
),其选项数据量超过 1 万条,而在测试环境中这些数据量只有几百条。这导致在页面加载时,浏览器性能出现瓶颈,页面卡顿甚至崩溃。
想了一下,如果接口支持搜索和分页,那直接通过上拉加载就可以了。但后端不太愿意改😄。行吧,前端搞也是可以的。
这个故事还有个后续:
过了一周上线后,发现有一个下拉框的数据有30w+!!!加载都加载不出来,哈哈哈哈,接口直接超时报错了,所以又cuocuocuo改了一遍,最后改成了:
- 接口翻页请求
- 前端使用自定义指令实现上拉加载更多,搜索直接走的后端接口
方案
通过一顿搜索加联想总结了3种方法,以下方法都需要支持开启filterable
支持搜索。
标题 | 具体 | 问题 |
---|---|---|
方案1 | 只展示前100条数据,这个的话配合filter-method 每次只返回前100条数据。 | 限制展示的条数可能不全,搜索需要多搜索点内容 |
方案2 | 分页方式,通过指令实现上拉加载,不断上拉数据展示数据。 | 仅过滤加载出来的数据,需要配合filterMethod过滤数据 |
方案3 | options列表采用虚拟列表实现。 | 成本高,需要引入虚拟列表组件或者自己手写。经掘友指点,发现element-plus提供了对应的实现,如果是plus,则可以直接使用select-v2。 |
方案一、 filterMethod直接过滤数据量
<template>
<el-select
v-model="value"
clearable filterable
:filter-method="filterMethod">
<el-option
v-for="(item, index) in options.slice(0, 100)"
:key="index"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</template>
export default {
name: 'Demo',
data() {
return {
options: [],
value: ''
}
},
beforeMount() {
this.getList();
},
methods: {
// 模拟获取大量数据
getList() {
for (let i = 0; i < 25000; i++) {
this.options.push({label: "选择"+i,value:"选择"+i});
}
},
filterMethod(val) {
console.log('filterMethod', val);
this.options = this.options.filter(item => item.value.indexOf(val) > -1).slice(0, 100);
},
visibleChange() {
console.log('visibleChange');
}
}
}
方案二、自定义滚动指令,实现翻页加载
写自定义滚动指令,options列表滚动到底部后,再加载下一页。但这时候筛选出来的是已经滚动出来的值。
这里如果直接使用filterable来搜索,搜索出来的内容是已经滑动出来的内容。如果想筛选全部的,就需要重写filterMethod方法来自定义过滤功能。可以根据情况选择是否要重写filterMethod。
<template>
<div class="dashboard-editor-container">
<el-select
v-model="value"
clearable
filterable
v-el-select-loadmore="loadMore"
:filter-method="filterMethod">
<el-option
v-for="(item, index) in options"
:key="index"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
</template>
<script>
export default {
name: 'Demo',
data() {
return {
options: [],
value: '',
pageNo: 0
}
},
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() {
this.getPageList();
},
filterMethod(val) {
this.data = val ? this.allData.filter(item => item.label.indexOf(val) > -1) : this.allData;
this.getPageList();
}
},
directives:{
'el-select-loadmore':(el, binding) => {
// 获取element-ui定义好的scroll父元素
const wrapEl = el.querySelector(".el-select-dropdown .el-select-dropdown__wrap");
if(wrapEl){
wrapEl.addEventListener("scroll", function () {
/**
* scrollHeight 获取元素内容高度(只读)
* scrollTop 获取或者设置元素的偏移值,
* 常用于:计算滚动条的位置, 当一个元素的容器没有产生垂直方向的滚动条, 那它的scrollTop的值默认为0.
* clientHeight 读取元素的可见高度(只读)
* 如果元素滚动到底, 下面等式返回true, 没有则返回false:
* ele.scrollHeight - ele.scrollTop === ele.clientHeight;
*/
if (this.scrollTop + this.clientHeight >= this.scrollHeight) {
// binding的value就是绑定的loadmore函数
binding.value();
}
});
}
},
},
}
</script>
</script>
方案三、虚拟列表
引入社区的vue-virtual-scroll-list 支持虚拟列表。这里想的自己再实现一遍虚拟列表,后续再写吧。
另外,element-plus提供了对应的实现,如果是使用的是plus,则可以直接使用 select-v2组件。
<template>
<div class="dashboard-editor-container">
<el-select
v-model="value"
clearable
filterable >
<virtual-list
class="list"
style="height: 360px; overflow-y: auto;"
:data-key="'value'"
:data-sources="data"
:data-component="item"
:estimate-size="50"
/>
</el-select>
</div>
</template>
<script>
import VirtualList from 'vue-virtual-scroll-list';
import Item from './item';
export default {
name: 'Demo',
components: {VirtualList, Item},
data() {
return {
options: [],
data: [],
value: '',
pageNo: 0,
item: Item,
}
},
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() {
this.getPageList();
}
}
}
</script>
// item组件
<template>
<el-option :label="source.label" :value="source.value"></el-option>
</template>
<script>
export default {
name: 'item',
props: {
source: {
type: Object,
default() {
return {}
}
}
}
}
</script>
<style scoped>
</style>
总结
最后我们项目中使用的虚拟列表,为啥,因为忽然发现组件库支持select是虚拟列表,那就直接使用这个啦。
最后的最后
没有用虚拟列表,因为接口数据量过大(你见过返回30w+的接口吗🙄。。),后端接口改成分页,前端支持自定义指令上拉加载,引用的参数增加了remote、remote-method设置为远端的方法。
<template>
<div class="dashboard-editor-container">
<el-select
v-model="value"
clearable
filterable
v-el-select-loadmore="loadMore"
remote
:remote-method="remoteMethod">
<el-option
v-for="(item, index) in options"
:key="index"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
</template>
参考文章:
- 解决 Element-ui中 选择器(Select)因options 数据量大导致渲染慢、页面卡顿的问题
- 对el-select进行二次封装,解决数据量过大造成页面卡顿问题