赞
踩
对于体量相对庞大的公司来说,数据量很多都是数以万计的,对于一些选择组件例如select来说,同时渲染这么庞大的数据往往会导致页面卡顿甚至会直接卡死。
页面卡顿的根本原因其实是浏览器渲染的时候数据量过大导致的,我们可以将数据进行分段处理,不需要展示所有,只展示部分数据,等滑到底部再加载更多,这样每次浏览器只需要渲染小部分数据。
触底事件触发
既然需要进行分段处理,那么首先就需要一个触发事件,当数据加载完成时获取更多数据进行加载,这里需要的就是一个触底事件。判断元素是否触底的办法有很多,大多都是通过计算元素高度,滚动条高度等计算得到,这种方法不但会有几px的误差,还需要给添加滚动事件实时监听。所以我们采用IntersectionObserver来实现。这个api会在元素出现在页面的时候触发,那么我们只需要在下拉框option元素下面添加一个空的元素,就可以完美做到触底监听。只要这个元素出现在屏幕,就表示触底,就触发IntersectionObserver这个api。兼容查询方法
大多时候这种数据量大的下拉框,我们还会提供前端模糊查询的方式,这个时候我们还需要维护一份模糊查询的数据,以便查询的时候触底加载已查询出来的数据数据回显
当我们需要对下拉框进行编辑操作的时候,会发现当前数据不一定能正常回显,因为我们每次只渲染部分数据,触底加载更多,很有可能需要回显的数据这个时候并没有被渲染。所以我们需要监听当下拉数据获取到值,并且绑定的值也同时存在的时候,那就是需要回显的时候,我们需要手动在所有下拉数据里面找到需要回显的那条,并把它移动到列表第一个,这样当第一次加载就可以回显成功!<!-- 该组件用于处理大量下拉数据(几千上万条)导致页面卡顿的情况 数据不多时不推荐使用,因为多了一些处理逻辑理论上来说会比el-select慢 --> <!-- 该组件用于处理大量下拉数据(几千上万条)导致页面卡顿的情况 数据不多时不推荐使用,因为多了一些处理逻辑理论上来说会比el-select慢 --> <template> <el-select ref="elSelect" :value="value" :filter-method="selfFilter" :filterable="filterable" :clearable="clearable" :noDataText="emptyText" v-bind="$attrs" v-on="$listeners" @remove-tag="removeTag" @change="selectChange" @visible-change="visibleChange" > <template v-for="(value, name) in $slots" #[name]> <slot :name="name"> </slot> </template> <el-option v-for="item in showOptions" :key="item[optionValue]" :value="item[optionValue]" :label="item[optionLabel]" > <slot name="option" :data="item"></slot> </el-option> <span ref="endOption" v-show="selectVisible"></span> </el-select> </template> <script> export default { name: 'ClMuchSelect', npmUp: true, props: { value: null, // 接口拿到的所有数据 options: { type: Array, default: () => [] }, // 下拉label optionLabel: { type: String, default: 'label' }, // 用例进行存储的字段 optionValue: { type: String, default: 'value' }, // 第一次展示的条数 showListNum: { type: Number, default: 20 }, // 一次加载的条数 loadListNum: { type: Number, default: 20 } }, data() { return { // 拷贝出的原生所有数据 copyOriginalList: [], // 下拉渲染的数据 showOptions: [], // 查询出的所有数据 filterOption: [], // 触底元素 endEle: null, // 用来判断是否为初始化观察者,此时不应该执行列表加载方法 initObserver: true, // 观察者 observer: null, // 是否处于查询 isFilter: false, selectVisible: false } }, mounted() { this.$nextTick(() => { this.endEle = this.$refs.endOption this.endEle ? this.createObserver() : null }) }, beforeDestroy() { this.delObserver() }, computed: { curLength() { return this.showOptions.length }, optionsLength() { return this.options.length }, filterable() { return this.$attrs.filterable === false ? false : true }, clearable() { return this.$attrs.clearable === false ? false : true }, multiple() { return this.$attrs.multiple === false ? false : true }, // 没有数据或者没有查询到数据的时候的文本展示 emptyText() { // 开启查询且原始列表有值且展示列表无数据则表示没有筛选出数据 if (this.filterable && this.optionsLength > 0 && this.curLength === 0) { return this.$attrs.noMatchText || this.$attrs.noDataText } else { return this.$attrs.noDataText } } }, methods: { // 初始化下拉列表 initList() { this.isFilter = false this.filterOption = [] if (this.value) { // 如果是多选且有值需要回显 if (this.multiple && this.value.length > 0) { let ids = [] let optionLength = this.copyOriginalList.length for (let index = 0; index < optionLength; index++) { const item = this.copyOriginalList[index] if (this.value.includes(item[this.optionValue])) { ids.push(index) } if (ids.length === optionLength) { break } } ids.forEach((i) => { this.copyOriginalList.splice( 0, 0, this.copyOriginalList.splice(i, 1)[0] ) }) } else if ( ['string', 'number', 'boolean'].includes(typeof this.value) ) { let index = this.copyOriginalList.findIndex( (item) => item[this.optionValue] === this.value ) if (index >= 0) { this.copyOriginalList.splice( 0, 0, this.copyOriginalList.splice(index, 1)[0] ) } } } this.showOptions = this.copyOriginalList.slice(0, this.showListNum) }, // 解决select出现立即触发addOption的bug visibleChange(flag) { if (flag) { setTimeout(() => { this.selectVisible = flag }, 100) } else { this.selectVisible = false } }, selectChange(val) { this.$emit('input', val) }, // 重写过滤方法 selfFilter(val) { if (val) { this.isFilter = true } else { this.isFilter = false this.showOptions = [] this.filterOption = [] } // 如果需要使用filterMethod属性自定义搜索,需要返回一个值作为搜索后的结果接收。 if (typeof this.$attrs['filter-method'] === 'function') { this.filterOption = this.$attrs['filter-method']( val, this.copyOriginalList ) } else { // 默认按照输入的文本进行过滤 this.filterOption = this.copyOriginalList.filter((item) => { return String(item[this.optionLabel]).includes(val) }) } this.showOptions = this.filterOption.slice(0, this.showListNum) }, // 到底部新加数据 addOption() { if (!this.selectVisible) { return } let listArr = [] if (this.isFilter) { listArr = this.filterOption } else { listArr = this.copyOriginalList } if (this.showOptions.length < listArr.length) { this.showOptions = this.showOptions.concat( listArr.slice(this.curLength, this.curLength + this.loadListNum) ) } }, // 多选模式下的移除操作 removeTag(val) { let oldIndex = this.options.findIndex( (item) => item[this.optionValue] === val ) let newIndex = this.copyOriginalList.findIndex( (item) => item[this.optionValue] === val ) this.copyOriginalList.splice( oldIndex, 0, this.copyOriginalList.splice(newIndex, 1)[0] ) // 注意此刻下拉值并没有被完全删除,打印发现值还是未删除之前的值,删除操作应该是在调用该方法之后执行的 // 所有使用nextTick将初始化操作放到下个队列里 this.$nextTick(() => { this.initList() }) }, // 创建观察器 createObserver() { this.observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { this.addOption() } }) }) this.observer.observe(this.endEle) }, // 销毁观察者 delObserver() { if (this.observer) { this.observer.unobserve(this.endEle) } } }, watch: { options: { deep: true, immediate: true, handler(value) { // 将原生数据拷贝到本地 this.copyOriginalList = JSON.parse(JSON.stringify(value)) this.initList() } } } } </script> </script>
<template>
<div>
<MuchSelect v-model="val1" :options="options" />
</div>
</template>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。