写到前面
antd相信大家都不陌生,在众多开源组件库有着举足轻重的地位;Select 选择器是组件库中常用的组件之一,如何在大数据同样丝滑的使用 select 便成为一个有意思的事情!
方法一:分批加载数据
分页加载数据即是分页加载列表数据,每次多加载一页的数据,依次递增;此方式用 antd 提供的api popupScroll计算列表滑动到底部后多渲染一页的数据;代码如下:
<template>
<div id="app">
<a-select
mode="tags"
style="width: 100%"
placeholder="Tags Mode"
@change="handleChange"
@popupScroll="handleScroll"
>
<a-select-option v-for="i in list" :key="(i + 9).toString(36) + i">
{{ i }}
</a-select-option>
</a-select>
</div>
</template>
<script>
// import HelloWorld from './components/HelloWorld.vue'
const list = Array.from(Array(10000), (c, i) => i + 1);
const pageSize = 20;
export default {
name: 'App',
components: {
// HelloWorld
},
data() {
return {
// list: [1, 2, 3, 4, 5, 6, 7, 8, 9],
pageNum: 1,
}
},
computed: {
list() {
return list.slice(0, this.pageNum * pageSize)
}
},
methods: {
handleChange(value) {
console.log(`selected ${value}`);
},
handleScroll(e) {
// e.persist();
const { target } = e;
// scrollHeight:代表包括当前不可见部分的元素的高度
// scrollTop:代表当有滚动条时滚动条向下滚动的距离,也就是元素顶部被遮住的高度
// clientHeight:包括padding但不包括border、水平滚动条、margin的元素的高度
const rmHeight = target.scrollHeight - target.scrollTop;
const clHeight = target.clientHeight;
// 当下拉框失焦的时候,也就是不下拉的时候
if (rmHeight === 0 && clHeight === 0) {
console.log('stopScroll');
} else {
// 滚动到底部
if (rmHeight < clHeight + 5) {
// dispatchState({ type: 'scroll' });
this.pageNum += 1
}
}
}
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
此方法比较简单,缺点也比较明显
- 数据超过500条左右时虽然每次只多加载一页的数据但会出现渲染较慢的体感,目测数据量大了vue的dom-diff也会消耗部分时间
- 取消焦点后重新focus表单,列表卡顿依然比较明显
方法二:自定义下拉列表(虚拟列表)
问题原因归根结底是渲染较大数据的列表(长列表),既然是长列表自然就想到了虚拟列表的优化方案;使用虚拟列表渲染select下拉列表岂不美哉。。。
只需要三步即可完成:
- 使用
vue-virtual-scroller渲染下拉列表,借用 select 提供的dropdownRenderapi自定义下拉菜单 - 自定义单条item
click事件 - 自定义 select 选中 代码如下:
<template>
<div id="app">
<a-select
:value="value"
style="width: 100%"
placeholder="Tags Mode"
>
<template v-if="list.length">
<div slot="dropdownRender" class="custom-dropdown">
<RecycleScroller
class="scroller"
:items="list"
key-field="id"
:item-size="32"
>
<template slot-scope="{ item }">
<li
class="dropdown-item"
@click="dropdownItemHandler(item)"
>
{{ item }}
</li>
</template>
</RecycleScroller>
</div>
</template>
</a-select>
</div>
</template>
<script>
const list = Array.from(Array(10000), (c, i) => i + 1);
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
import { RecycleScroller } from 'vue-virtual-scroller';
export default {
name: 'App',
components: {
RecycleScroller,
},
data() {
return {
value: undefined,
list: list,
}
},
methods: {
dropdownItemHandler(value) {
console.log(`selected ${value}`);
this.value = value;
},
},
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.custom-dropdown {
height: 250px;
}
.custom-dropdown .scroller {
height: 100%;
overflow-y: scroll;
}
.custom-dropdown .dropdown-item {
padding: 5px 12px;
color: rgba(0, 0, 0, 0.65);
}
.custom-dropdown .dropdown-item:hover {
background-color: #e6f7ff;
}
</style>
此方法实现的效果相对比较完美,不再受数据量大小的限制;虽然需要自定义部分select已有的功能😛😛😛
写到最后
这里就不多废话,相信看完上边内容的同学对于两种方法的优劣自有见解,本文仅是解决问题的记录;代码传送门