当前位置:   article > 正文

el-select 性能优化_el-select大数据优化

el-select大数据优化

目录

1.目的

2.原理

3.优点

4.源码

1)安装插件

2) 创建全局组件VirtualListSelect。

a.简单页面也即正常开发的页面中

b.通过表单配置在页面中渲染出来的下拉框


1.目的

为了解决 element-ui 中 el-select 组件在大数据量的情况下出现的性能问题(数据量太大,导致渲染过慢,或造成页面卡顿甚至于卡死) 。

2.原理

模拟虚拟滚动,对 el-select组件结合vue-virtual-scroll-list(vue虚拟列表)插件进行二次封装

3.优点

适用于只查询一次接口,后端一次性把数据返回。 对比其他的优化方案,有以下优点

方案一:后台进行分页;这种。。嗯,那后端人员可能不乐意了,心里想我都把数据返回给你了,加载慢,页面卡顿不应该你前端的问题吗(关我什么事)。ok,为了避免这种情况,我们就不麻烦后端同学在每个下拉框数据返回后进行分页了,那可是个大工程。对比这种方案优点就是

  • 无需后端进行配合,只需要初始化的时候一次性把数据返回给前端即可

方案二:前端懒加载,这种确实可以,我不用此方案就是考虑到数据量过大,那即使页面初始加载的时候较快,但是在页面销毁,简单来说就是切页面的时候,你要销毁所有的dom节点那肯定会出现你的路由已经变化了,但是你的页面还没跳转,造成用户体验不好。对比这种方案我的方案优点

  • 由于不管下拉框如何下拉,至始至终都只渲染自己设置的规定的数量,比如:keeps="20"那这个下拉框始终只会渲染20条下拉数据,vue种dom节点那就渲染20条,对比成千上万,不论是初始化或者销毁速度都会明显的提升,用户体验好。

4.源码

1)安装插件

由于需要使用插件vue-virtual-scroll-list,所以我们先在项目中把插件安装一下;

插件官网地址:vue-virtual-scroll-list - npmvue-virtual-scroll-list - npmvue-virtual-scroll-list - npm

npm install vue-virtual-scroll-list --save

2) 创建全局组件VirtualListSelect。

我使用的环境相对来说比较复杂 ,因为我封装的这个组件主要用在低代码平台中,即下拉框是通过页面配置然后渲染出来的,所以需要接收许多参数,但是在正常开发的页面中不需要接收这么多参数,为了方便读者借鉴,我就两种都记录一下。正常开发的页面已经满足大部分读者的需求。

a.简单页面也即正常开发的页面中

  •  在src/components下创建VirtualListSelect文件夹,文件夹下大概格式如下:

  •  VirtualListSelect.vue文件
  1. <template>
  2. <div>
  3. <el-select
  4. :value="defaultValue"
  5. popper-class="virtualselect"
  6. filterable
  7. :filter-method="filterMethod"
  8. @visible-change="visibleChange"
  9. v-bind="$attrs"
  10. v-on="$listeners"
  11. >
  12. <virtual-list
  13. ref="virtualList"
  14. class="virtualselect-list"
  15. :data-key="selectData.value"
  16. :data-sources="selectArr"
  17. :data-component="itemComponent"
  18. :keeps="20"
  19. :extra-props="{
  20. label: selectData.label,
  21. value: selectData.value,
  22. isRight: selectData.isRight
  23. }"
  24. ></virtual-list>
  25. </el-select>
  26. </div>
  27. </template>
  28. <script>
  29. import VirtualList from 'vue-virtual-scroll-list';
  30. import itemComponent from './itemComponent';
  31. export default {
  32. name: 'Select',
  33. components: {
  34. 'virtual-list': VirtualList
  35. },
  36. model: {
  37. prop: 'defaultValue',
  38. event: 'change'
  39. },
  40. props: {
  41. selectData: {
  42. type: Object,
  43. default() {
  44. return {};
  45. }
  46. }, //父组件传的值
  47. defaultValue: {
  48. type: String,
  49. default: []
  50. } // 绑定的默认值
  51. },
  52. mounted() {
  53. this.init();
  54. },
  55. watch: {
  56. 'selectData.data'() {
  57. this.init();
  58. }
  59. },
  60. data() {
  61. return {
  62. itemComponent: itemComponent,
  63. selectArr: []
  64. };
  65. },
  66. methods: {
  67. init() {
  68. if (!this.defaultValue || this.defaultValue.length === 0) {
  69. this.selectArr = this.selectData.data;
  70. } else {
  71. // 回显问题
  72. // 由于只渲染20条数据,当默认数据处于20条之外,在回显的时候会显示异常
  73. // 解决方法:遍历所有数据,将对应回显的那一条数据放在第一条即可
  74. // 注意:此例子只有单选情况,多选类似,想看实现代码请在低代码情况下会有完善
  75. this.selectArr = JSON.parse(JSON.stringify(this.selectData.data));
  76. let obj = {};
  77. for (let i = 0; i < this.selectArr.length; i++) {
  78. const element = this.selectArr[i];
  79. if (
  80. element[this.selectData.value].toLowerCase() ===
  81. this.defaultValue.toLowerCase()
  82. ) {
  83. obj = element;
  84. this.selectArr.splice(i, 1);
  85. break;
  86. }
  87. }
  88. this.selectArr.unshift(obj);
  89. }
  90. },
  91. // 搜索
  92. filterMethod(query) {
  93. if (query !== '') {
  94. this.$refs.virtualList.scrollToIndex(0); //滚动到顶部
  95. setTimeout(() => {
  96. this.selectArr = this.selectData.data.filter((item) => {
  97. return this.selectData.isRight
  98. ? item[this.selectData.label]
  99. .toLowerCase()
  100. .indexOf(query.toLowerCase()) > -1 ||
  101. item[this.selectData.value]
  102. .toLowerCase()
  103. .indexOf(query.toLowerCase()) > -1
  104. : item[this.selectData.label]
  105. .toLowerCase()
  106. .indexOf(query.toLowerCase()) > -1;
  107. });
  108. }, 100);
  109. } else {
  110. this.init();
  111. }
  112. },
  113. visibleChange(bool) {
  114. if (!bool) {
  115. this.$refs.virtualList.reset();
  116. this.init();
  117. }
  118. }
  119. }
  120. };
  121. </script>
  122. <style lang="scss">
  123. .virtualselect {
  124. // 设置最大高度
  125. &-list {
  126. max-height: 245px;
  127. overflow-y: auto;
  128. }
  129. .el-scrollbar .el-scrollbar__bar.is-vertical {
  130. width: 0 !important;
  131. }
  132. }
  133. </style>

  •  itemComponent.vue 文件

  1. <template>
  2. <div>
  3. <el-option
  4. :key="label + value"
  5. :label="source[label]"
  6. :value="source[value]"
  7. >
  8. <span>{{ source[label] }}</span>
  9. <span v-if="isRight" style="float:right;color:#939393">{{
  10. source[value]
  11. }}</span>
  12. </el-option>
  13. </div>
  14. </template>
  15. <script>
  16. export default {
  17. name: 'item-component',
  18. props: {
  19. // index of current item
  20. // 每一行的索引
  21. index: {
  22. type: Number
  23. },
  24. // 每一行的内容
  25. source: {
  26. type: Object,
  27. default() {
  28. return {};
  29. }
  30. },
  31. // 需要显示的名称
  32. label: {
  33. type: String
  34. },
  35. // 绑定的值
  36. value: {
  37. type: String
  38. },
  39. // 右侧是否显示绑定的值
  40. isRight: {
  41. type: Boolean,
  42. default() {
  43. return false;
  44. }
  45. }
  46. },
  47. mounted() {}
  48. };
  49. </script>

组件封装完成后,我们最好将其注册成全局组件,以便在系统中使用

  1. import VirtualListSelect from './VirtualListSelect';
  2. Vue.component('virtual-list-select', VirtualListSelect);

  • 下面写一个简单的demo 

demo.vue

  1. <template>
  2. <div class="cw-select">
  3. <virtual-list-select
  4. :selectData="selectData"
  5. v-model="defaultValue"
  6. multiple
  7. placeholder="请选择下拉数据"
  8. clearable
  9. @change="selectChange"
  10. ></virtual-list-select>
  11. </div>
  12. </template>
  13. <script>
  14. export default {
  15. name: 'virtual-list-select',
  16. data() {
  17. return {
  18. selectData: {
  19. data: [], // 下拉框数据
  20. label: 'name', // 下拉框需要显示的名称
  21. value: 'code', // 下拉框绑定的值
  22. isRight: false //右侧是否显示
  23. },
  24. defaultValue: [] //下拉框选择的默认值
  25. };
  26. },
  27. mounted() {
  28. this.selectData.data = [];
  29. for (let i = 0; i < 10000; i++) {
  30. this.selectData.data.push({ code: 'Test' + i, name: '测试' + i + '' });
  31. }
  32. },
  33. methods: {
  34. selectChange(val) {
  35. console.log('下拉框选择的值', val);
  36. }
  37. }
  38. };
  39. </script>

b.通过表单配置(低代码平台)在页面中渲染出来的下拉框

  • VirtualListSelect.vue
  1. <template>
  2. <div class="virtual-list-select">
  3. <el-select
  4. :value="value"
  5. popper-class="virtualselect"
  6. :multiple="multiple"
  7. :collapse-tags="collapseTags"
  8. :placeholder="placeholder"
  9. :clearable="clearable"
  10. :disabled="disabled"
  11. :filterable="filterable"
  12. :filter-method="filterMethod"
  13. :remote="remote"
  14. :remote-method="remoteMethod"
  15. @visible-change="visibleChange"
  16. v-bind="$attrs"
  17. v-on="$listeners"
  18. >
  19. <virtual-list
  20. ref="virtualList"
  21. class="virtualselect-list"
  22. :data-key="selectData.value"
  23. :data-sources="selectArr"
  24. :data-component="itemComponent"
  25. :keeps="20"
  26. :extra-props="{
  27. label: selectData.label,
  28. value: selectData.value,
  29. isRight: selectData.isRight
  30. }"
  31. ></virtual-list>
  32. </el-select>
  33. </div>
  34. </template>
  35. <script>
  36. import VirtualList from 'vue-virtual-scroll-list';
  37. import itemComponent from './itemComponent';
  38. export default {
  39. name: 'virtual-list-select',
  40. components: {
  41. 'virtual-list': VirtualList
  42. },
  43. model: {
  44. prop: 'value',
  45. event: 'input'
  46. },
  47. props: {
  48. selectData: {
  49. type: Object,
  50. default() {
  51. return {};
  52. }
  53. }, //父组件传的值
  54. value: {
  55. type: String,
  56. default: ''
  57. }, // 绑定的默认值
  58. multiple: {
  59. type: Boolean,
  60. default: false
  61. },
  62. placeholder: {
  63. type: String,
  64. default: ''
  65. },
  66. filterable: {
  67. type: Boolean,
  68. default: true
  69. },
  70. remote: {
  71. type: Boolean,
  72. default: false
  73. },
  74. remoteMethod: {
  75. type: Function,
  76. default: () => ''
  77. },
  78. clearable: {
  79. type: Boolean,
  80. default: false
  81. },
  82. collapseTags: {
  83. type: Boolean,
  84. default: false
  85. },
  86. disabled: {
  87. type: Boolean,
  88. default: false
  89. }
  90. },
  91. watch: {
  92. 'selectData.data'() {
  93. this.init();
  94. }
  95. },
  96. data() {
  97. return {
  98. itemComponent: itemComponent,
  99. selectArr: []
  100. };
  101. },
  102. methods: {
  103. init() {
  104. if (!this.value || this.value.length === 0) {
  105. this.selectArr = this.selectData.data;
  106. } else {
  107. /** 回显问题
  108. 由于只渲染20条数据,当默认数据处于20条之外,在回显的时候会显示异常
  109. 解决方法:遍历所有数据,将对应回显的那一条数据放在第一条即可 */
  110. this.selectArr = JSON.parse(JSON.stringify(this.selectData.data));
  111. if (!this.multiple) {
  112. // 1.单选
  113. let obj = {};
  114. for (let i = 0; i < this.selectArr.length; i++) {
  115. const element = this.selectArr[i];
  116. if (
  117. element[this.selectData.value]?.toLowerCase() ===
  118. this.value?.toLowerCase()
  119. ) {
  120. obj = element;
  121. this.selectArr.splice(i, 1);
  122. break;
  123. }
  124. }
  125. this.selectArr.unshift(obj);
  126. } else {
  127. // 2.多选
  128. const selectedArr = [];
  129. for (let i = 0; i < this.selectArr.length; i++) {
  130. const element = this.selectArr[i];
  131. for (let j = 0; j < this.value.length; j++) {
  132. const item = this.value[j];
  133. if (
  134. element[this.selectData.value]?.toLowerCase() ===
  135. item?.toLowerCase()
  136. ) {
  137. selectedArr.push(element);
  138. this.selectArr.splice(i, 1);
  139. break;
  140. }
  141. }
  142. }
  143. this.selectArr.unshift(...selectedArr);
  144. }
  145. }
  146. },
  147. // 搜索
  148. filterMethod(query) {
  149. if (query !== '' && !this.remote) {
  150. this.$refs.virtualList.scrollToIndex(0); //滚动到顶部
  151. setTimeout(() => {
  152. this.selectArr = this.selectData.data.filter((item) => {
  153. return this.selectData.isRight
  154. ? item[this.selectData.label]
  155. .toLowerCase()
  156. .indexOf(query.toLowerCase()) > -1 ||
  157. item[this.selectData.value]
  158. .toLowerCase()
  159. .indexOf(query.toLowerCase()) > -1
  160. : item[this.selectData.label]
  161. .toLowerCase()
  162. .indexOf(query.toLowerCase()) > -1;
  163. });
  164. }, 100);
  165. } else {
  166. this.init();
  167. }
  168. },
  169. visibleChange(bool) {
  170. if (!bool) {
  171. this.$refs.virtualList.reset();
  172. this.init();
  173. }
  174. }
  175. },
  176. created() {
  177. this.init();
  178. }
  179. };
  180. </script>
  181. <style lang="scss" scoped>
  182. .virtualselect {
  183. // 设置最大高度
  184. &-list {
  185. max-height: 245px;
  186. overflow-y: auto;
  187. }
  188. .el-scrollbar .el-scrollbar__bar.is-vertical {
  189. width: 0;
  190. }
  191. }
  192. </style>
  •  Readme.md文件
  1. ## VirtualListSelect
  2. ### 1. 组件说明
  3. * 本组件是对 el-select组件结合vue-virtual-scroll-list(vue虚拟列表)插件的二次封装。原理:模拟虚拟滚动,目的是为了解决 element-ui 中 el-select 组件在大数据量的情况下出现的性能问题(数据量太大,导致渲染过慢,造成页面卡顿甚至于卡死)。
  4. * 插件地址:https://www.npmjs.com/package/vue-virtual-scroll-list
  5. ### 2. 实现原理
  6. * 用vue-virtual-scroll-list这个插件去包裹需要循坏展示的标签。这里就是el-option标签。
  7. * 由于插件的 data-component 属性,需要抽离出el-option标签封装成一个组件
  8. ### 3. 属性说明
  9. * data-key=“‘id’” 就是绑定的唯一key值
  10. * data-sources=“selectArr” 下拉框的数组
  11. * data-component=“itemComponent” 就是抽离中的el-option组件
  12. * keeps=“20” 渲染的个数(默认30个)
  13. * extra-props 值为对象,可以传入自定义属性进去
  14. ### 4. 方法
  15. * 实现模糊搜索功能,使用el-select自带的filterMethod方法
  16. * visible-change事件实现下拉框出现/隐藏时触发虚拟列表重置和把列表重置成全量数据
  17. ### 5. 注意点
  18. 1. <virtual-list style="max-height: 245px; overflow-y: auto;"
  19. * 这里的样式一定要设置成最大高度,防止数据量少了时候下拉框显示多余空白地方
  20. * 高度要设置成245px,不然会出现两个滚动条,会发生滚动bug
  21. * 一定要设置y轴超出滚动
  22. * select标签使用popper-class自定义一个类名,解决会出现两个滚动条的问题
  23. 缺点:
  24. * 如果每个选项的长度差距过大,横向宽度会随着滚动变化,这是因为默认只加载20个选项,el-select又是根据所有的optiion中最长的进行填充,如果加载另外20条数据的长度过长时就会出现这种情况。
  25. * 由于只渲染20条数据,当默认数据处于20条之外,在回显的时候会显示异常,目前的解决方法是:遍历所有数据,将对应回显的那一条数据放在第一条即可。
  •  低代码中render函数

render.js

  1. export default {
  2. name: 'render',
  3. props: {
  4. type: String,
  5. config: Object
  6. },
  7. render: function(h) {
  8. switch (this.type) {
  9. case 'select':
  10. // return selectItem(h, this.config);
  11. return virtualListSelect(h, this.config);
  12. default:
  13. return '';
  14. }
  15. }
  16. };
  17. // 虚拟下拉列表
  18. function virtualListSelect(h, config) {
  19. const {
  20. model,
  21. key,
  22. props,
  23. listeners,
  24. options,
  25. optionConfig,
  26. placeholder,
  27. syncConfig,
  28. goods,
  29. value,
  30. multiple
  31. } = config;
  32. let opl = 'label';
  33. let opv = 'value';
  34. if (optionConfig) {
  35. if (optionConfig.label) {
  36. opl = optionConfig.label;
  37. }
  38. if (optionConfig.value) {
  39. opv = optionConfig.value;
  40. }
  41. }
  42. const opts = options;
  43. const on = listeners || {};
  44. let pps = props || {};
  45. if (syncConfig) {
  46. pps = {
  47. ...pps,
  48. ...syncConfig()
  49. };
  50. }
  51. return h('virtual-list-select', {
  52. props: {
  53. placeholder: placeholder || '请选择',
  54. filterable: true,
  55. ...pps,
  56. value: value,
  57. selectData: {
  58. data: opts, // 下拉框数据
  59. label: opl || 'text', // 下拉框需要显示的名称
  60. value: opv, // 下拉框绑定的值
  61. isRight: false //右侧是否显示
  62. },
  63. multiple,
  64. allowCreate: goods.allowCreate,
  65. disabled: goods.readable === 1 || goods.pageDisabled,
  66. 'multiple-limit': goods.itemMultipleLimit,
  67. 'collapse-tags': true,
  68. clearable: goods.clearAble,
  69. 'popper-append-to-body': false
  70. },
  71. on: {
  72. ...on,
  73. change: (val) => {
  74. goods.value = val;
  75. if (model) {
  76. model[key] = val;
  77. }
  78. if (on.change) {
  79. on.change(val);
  80. }
  81. }
  82. }
  83. });
  84. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/114771?site
推荐阅读