时间:2022-04-17 08:58:58 | 栏目:vue | 点击:次
不知道你有没有这样的经历,下拉框的选项很多,上万个选项甚至更多,这个时候如果全部把数据放到下拉框中渲染出来,浏览器会卡死,体验会特别不好
用人会说element-ui的select有一个remote-method,支持远程搜索,我们让服务端支持一下不就可以了,当然这是一种解决的方案。但是有时候这种方法有时候不一定适用
(1)有时候服务端数据是经过计算返回给我们的,可能返回不是特别快,体验不是很好
(2)有时候数据可能只有几千条,全部渲染又不太合适,一直掉接口不是特别好
(3)仅仅通过前端能不能解决,如果能解决,岂不是减轻了服务端的工作和压力
1 ) 分段加载:也不加载下拉项,通过点击下拉框的时候,再去加载,此时的选项全部加载进来,该种情况只适用于缓加载情况,需要点击加载完后才能下拉选项,体验一般。
2 )提示:element-ui的select有一个filter-method方法,我们可以通过这个方法来进行过滤下拉项
假设我们有个下拉框是用来选择用户的
<el-select v-model="userId" filterable :filter-method="userFilter" clearable> <el-option v-for="item in userList" :key="item.userId" :label="item.username" :value="item.userId" ></el-option> </el-select>
userFilter(query = '') { let arr = this.allUserList.filter((item) => { return item.username.includes(query) || item.userId.includes(query) }) if (arr.length > 50) { this.userList = arr.slice(0, 50) } else { this.userList = arr } }, getUserWhiteList() { HttpRequest.post("/api/admin/community/getUserWhiteList").then( response => { this.allUserList = response.data.list; this.userFilter() } ); },
如上所示,我们从后台获取用户列表,经过我们自己的过滤,我们每次只渲染50条数据,无论有多少数据,对我们来说也支持一个变量,占个内存。当然数据越多,数组的遍历也会相应的慢,但是这个影响不大。
我们不仅能过滤名字,还可以对我们制定的任一项进行过滤
优化:上面的代码我们还可以适当优化下,只有发现了数组长度超过了50项,我们就停止遍历
当使用el-select组件时,如果options数量过多,会存在的弊端:
页面渲染出大量el-option节点,会导致页面卡顿甚至卡死,用户体验极差。
选择时条目众多,查找困难。
本次我遇到的场景是options数量为6-9千的情况。
从总options中取出固定条目的小option(renderOption)用于页面渲染,利用el-select提供的
filter-method方法进行搜索过滤,在搜索时用过滤结果更新renderOption。
代码实现
下面是vue的组件封装
<template> <el-select class="yt-select" v-model="currValue" filterable v-bind="$attrs" :filter-method="userFilter" :disabled="disabled" :clearable="clearable" @change="change" > <el-option v-for="option in renderOption" :key="option.value" :value="option.value" :label="option.label" >{{ option.label }}</el-option> </el-select> </template> <script> export default { name: 'easy-select', props: { value: { type: [String, Number], default: '' }, max: { type: Number, default: 30 }, disabled: { type: Boolean, default: false }, clearable: { type: Boolean, default: true }, options: { type: Array, default: () => [] } }, data () { return { renderOption: [] } }, computed: { currValue: { get () { return this.value || '' }, set (value) { this.$emit('input', value) } } }, watch: { value () { this.addValueOptions() }, options: { handler (V) { this.init() }, deep: true } }, created () { this.init() }, methods: { async init () { this.userFilter() this.addValueOptions() }, addValueOptions () { if (this.currValue) { let target = this.options.find((item) => { // 从大option中找到当前条 return item.value === this.currValue }) if (target) { // 将当前条与小option比对,没有则加入 if (this.renderOption.every(item => item.value !== target.value)) { this.renderOption.unshift(target) } } } }, addFilterOptions (label) { // 每次查找输入时,若有精确匹配的条目,保证该条目一定在renderOption内 let target = this.options.find((item) => { // 从大option中找到当前条 return item.label === label }) if (target) { // 将当前条与小option比对,没有则加入 if (this.renderOption.every(item => item.label !== target.label)) { this.renderOption.unshift(target) } } }, userFilter (query = '') { let arr = this.options.filter((item) => { return item.label.includes(query) || item.value.includes(query) }) if (arr.length > this.max) { this.renderOption = arr.slice(0, this.max) this.addFilterOptions(query) } else { this.renderOption = arr } }, change (value) { this.$emit('change', value) if (!value) { // 单选清空-optons初始化下 this.userFilter() } } } } </script>