vue插件 vue-virtual-scroll-list解决数据量太大问题
项目中数据量有时候过于庞大,使用elementui的Select组件时,会导致下拉框加载速度慢卡顿甚至于卡死,为解决这个问题,使用vue-virtual-scroll-list插件 ,模拟虚拟滚动。
vue-virtual-scroll-list是vue的一个虚拟滚动组件,通过不渲染可视区域以外的内容,显示虚拟的滚动条来提升页面性能
首先看
安装
npm install vue-virtual-scroll-list --save
参数及方法
常用参数
Prop | Type | Description |
---|---|---|
data-key | String|Function | 从data-sources每个数据对象中获取唯一键。或每个函数都调用data-source并返回其唯一键。在中,其值必须唯一data-sources,用于标识商品尺寸。 |
data-sources | Array[Object] | 列表数据,每一行都必须有一个唯一的id(data-key) |
data-component | Component | 每一行的子组件 |
keeps | Number | 默认30个,默认渲染的个数 |
extra-props | Object | 默认{} data-component组件的额外props通过改属性传入,内部已有source和index两个 |
常用方法
可以通过以下方式调用这些方法ref:
Method | Description |
---|---|
reset() | 将所有状态重置为初始状态。 |
scrollToIndex(index) | 手动设置滚动位置到指定索引。 |
上述是组件用到的方法,其他参数及方法可查看官网
引入使用
1 import virtualList from 'vue-virtual-scroll-list' 2 components:{ 3 'virtual-list': virtualList 4 },
基础用法
- 属性说明
data-key=“selectData.value” 就是绑定的唯一key值,可传入动态的
data-sources=“selectArr” 下拉框的数组
data-component=“itemComponent” 就是抽离中的el-option组件
keeps=“20” 渲染的个数
extra-props 值为对象,可以传入自定义属性进去
- 高度设置
1 .virtualselect { 2 // 设置最大高度 3 &-list { 4 max-height:245px; 5 overflow-y:auto; 6 } 7 .el-scrollbar .el-scrollbar__bar.is-vertical { 8 width: 0; 9 } 10 }
原理
只渲染keeps传入的个数,滚动时通过改变padding的值来模拟滚动,里面的每一个item在滚动时动态替换里面的值,永远只渲染keeps传入的个数,所以这样不会卡
- 回显问题
由于只渲染20条数据,当默认数据处于20条之外,在回显的时候会显示异常
解决方法: 遍历所有数据,将对应回显的那一条数据放在第一条即可
1 this.selectArr = JSON.parse(JSON.stringify(this.selectData.data)); 2 let obj = {}; 3 for (let i = 0; i < this.selectArr.length; i++) { 4 const element = this.selectArr[i]; 5 if(element[this.selectData.value].toLowerCase() === this.defaultValue.toLowerCase()) { 6 obj = element; 7 this.selectArr.splice(i,1); 8 break; 9 } 10 } 11 this.selectArr.unshift(obj);
- 模糊搜索
使用select自带的filterable,filter-method自定义属性实现过滤数据
1 <el-select filterable :filter-method="filterMethod" @visible-change="visibleChange" > 2 </el-select> 3 4 // 搜索 5 filterMethod(query) { 6 if (query !== '') { 7 this.$refs.virtualList.scrollToIndex(0);//滚动到顶部 8 setTimeout(() => { 9 this.selectArr = this.selectData.data.filter(item => { 10 return this.selectData.isRight? 11 (item[this.selectData.label].toLowerCase().indexOf(query.toLowerCase()) > -1 || item[this.selectData.value].toLowerCase().indexOf(query.toLowerCase()) > -1) 12 :item[this.selectData.label].toLowerCase().indexOf(query.toLowerCase()) > -1; 13 }); 14 },100) 15 } else { 16 this.init(); 17 } 18 }, 19 visibleChange(bool) { 20 if(!bool) { 21 this.$refs.virtualList.reset(); 22 this.init(); 23 } 24 }
完整代码
- Select.vue
1 <template> 2 <div> 3 <el-select :value="defaultValue" popper-class="virtualselect" filterable :filter-method="filterMethod" @visible-change="visibleChange" v-bind="$attrs" v-on="$listeners"> 4 <virtual-list ref="virtualList" class="virtualselect-list" 5 :data-key="selectData.value" 6 :data-sources="selectArr" 7 :data-component="itemComponent" 8 :keeps="20" 9 :extra-props="{ 10 label: selectData.label, 11 value: selectData.value, 12 isRight: selectData.isRight 13 }"></virtual-list> 14 </el-select> 15 </div> 16 </template> 17 <script> 18 import virtualList from 'vue-virtual-scroll-list' 19 import ElOptionNode from './el-option-node' 20 export default { 21 components:{ 22 'virtual-list': virtualList 23 }, 24 model: { 25 prop: 'defaultValue', 26 event: 'change', 27 }, 28 props: { 29 selectData: { 30 type: Object, 31 default () { 32 return {} 33 } 34 },//父组件传的值 35 defaultValue: { 36 type: String, 37 default: '' 38 }// 绑定的默认值 39 }, 40 mounted() { 41 this.init(); 42 }, 43 watch: { 44 'selectData.data'() { 45 this.init(); 46 } 47 }, 48 data() { 49 return { 50 itemComponent: ElOptionNode, 51 selectArr:[] 52 } 53 }, 54 methods: { 55 init() { 56 if(!this.defaultValue) { 57 this.selectArr = this.selectData.data; 58 }else { 59 // 回显问题 60 // 由于只渲染20条数据,当默认数据处于20条之外,在回显的时候会显示异常 61 // 解决方法:遍历所有数据,将对应回显的那一条数据放在第一条即可 62 this.selectArr = JSON.parse(JSON.stringify(this.selectData.data)); 63 let obj = {}; 64 for (let i = 0; i < this.selectArr.length; i++) { 65 const element = this.selectArr[i]; 66 if(element[this.selectData.value].toLowerCase() === this.defaultValue.toLowerCase()) { 67 obj = element; 68 this.selectArr.splice(i,1); 69 break; 70 } 71 } 72 this.selectArr.unshift(obj); 73 } 74 }, 75 // 搜索 76 filterMethod(query) { 77 if (query !== '') { 78 this.$refs.virtualList.scrollToIndex(0);//滚动到顶部 79 setTimeout(() => { 80 this.selectArr = this.selectData.data.filter(item => { 81 return this.selectData.isRight? 82 (item[this.selectData.label].toLowerCase().indexOf(query.toLowerCase()) > -1 || item[this.selectData.value].toLowerCase().indexOf(query.toLowerCase()) > -1) 83 :item[this.selectData.label].toLowerCase().indexOf(query.toLowerCase()) > -1; 84 }); 85 },100) 86 } else { 87 this.init(); 88 } 89 }, 90 visibleChange(bool) { 91 if(!bool) { 92 this.$refs.virtualList.reset(); 93 this.init(); 94 } 95 } 96 } 97 } 98 </script> 99 <style lang="scss" scoped> 100 .virtualselect { 101 // 设置最大高度 102 &-list { 103 max-height:245px; 104 overflow-y:auto; 105 } 106 .el-scrollbar .el-scrollbar__bar.is-vertical { 107 width: 0; 108 } 109 } 110 111 </style>
-
el-option-node.vue
1 <template> 2 <el-option :key="label+value" :label="source[label]" :value="source[value]"> 3 <span>{{source[label]}}</span> 4 <span v-if="isRight" style="float:right;color:#939393">{{source[value]}}</span> 5 </el-option> 6 </template> 7 <script> 8 export default { 9 name: 'item-component', 10 props: { 11 index: { 12 type: Number 13 },// 每一行的索引 14 source: { 15 type: Object, 16 default () { 17 return {} 18 } 19 },// 每一行的内容 20 label: { 21 type: String 22 },// 需要显示的名称 23 value: { 24 type: String 25 },// 绑定的值 26 isRight: { 27 type: Boolean, 28 default () { 29 return false 30 } 31 }// 右侧是否显示绑定的值 32 } 33 } 34 </script>
-
parent.vue
1 <template> 2 <cw-select :selectData="selectData" v-model="defaultValue" placeholder="请选择下拉数据" clearable @change="selectChange"></cw-select> 3 </template> 4 5 <script> 6 import CwSelect from '@/components/Select' 7 export default { 8 components: { 9 CwSelect 10 }, 11 data() { 12 return { 13 selectData: { 14 data:[],// 下拉框数据 15 label: 'name',// 下拉框需要显示的名称 16 value: 'code',// 下拉框绑定的值 17 isRight: true,//右侧是否显示 18 }, 19 defaultValue: 'China99', //下拉框选择的默认值 20 }; 21 }, 22 mounted() { 23 this.selectData.data = [] 24 for (let i = 0; i < 10000; i++) { 25 this.selectData.data.push({code:'China'+i,name:'中国'+i+''}) 26 } 27 }, 28 methods: { 29 selectChange(val) { 30 console.log('下拉框选择的值', val) 31 } 32 } 33 }; 34 </script>