最近使用elementUI的el-select组件的远程查询功能,发现在大量数据(1w条以上)的时候出现页面卡死的情况,经过排查,产生问题的原因是前端渲染造成的。
1.在查找解决方案的时候看到一个解决办法是加载前50条,效果是这样的:
2.上面那个方法已经解决了页面卡死的问题了,但是只显示了50条结果,其他的结果无法显示。
顺着这个思路,我给最后一行(“只显示前50条…”)加了一个点击事件,鼠标点击的时候就多显示20条,
效果是这样的:
3.再升级一下,可不可以不用点击?
那就得加鼠标滚动事件,通过鼠标滚动加载更多数据,效果与第2点一样,只是不用点击就可以加载了。
组件代码:
<template>
<div>
<el-form ref="remoteForm" :model="remoteForm" :rules="formRule">
<el-form-item prop="result">
<el-tooltip ref="toolTip" :content="remoteForm.result?remoteForm.result:placeholder" popper-class="remote-tooltip" placement="top" effect="light">
<el-select
v-if="hasRemote"
id="remote-search"
ref="remoteSearch"
v-model="remoteForm.result"
filterable
remote
clearable
reserve-keyword
:placeholder="placeholder"
:size="size"
class="remote-search"
:remote-method="remoteMethod"
:loading="loading"
:title="result"
@input.native="input"
@change="change"
@focus="focus"
@mousewheel="mouseWheel"
>
<el-option
v-for="item in numberList_"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<div v-if="numberList.length > 9 && numberList_.length !== numberList.length" ref="moreDiv" class="search-keyword">
<span @click="loadMore">加载更多...span>
div>
<div v-else class="search-keyword-bottom">
<span>到底啦~span>
div>
el-select>
el-tooltip>
el-form-item>
el-form>
div>
template>
<script>
/**
* 1.如何清除表单?
* 使用v-if属性对组件进行创建和销毁。使用方法:传入:has-remote="hasRemote",
* hasRemote为true是创建组件,表单重置时使hasRemote=false;
* *如果是在对话框中,讲对话框的visible的值给has-remote即可
*/
export default {
name: 'RemoteSearch',
props: {
numberList: { // 完整的查询结果
default: () => [],
type: Array
},
size: {
default: 'mini',
type: String
},
placeholder: {
default: null,
type: [Number, String]
},
hasRemote: {
default: true,
type: Boolean
}
},
data() {
return {
loading: false,
options: [],
list: [],
searchData: '',
remoteForm: {
result: ''
},
formRule: {},
numberList_: [],
i: 1
}
},
computed: {
result() {
return this.remoteForm.result
}
},
watch: {
numberList() {
this.numberList_ = this.numberList.slice(0, 10) // 先加载10条结果
this.i = 1
},
result(newValue, oldValue) {
this.searchData = newValue
},
hasRemote() {
if (!this.hasRemote) {
this.$refs.remoteForm.resetFields() // 组件销毁时重置输入框
// window.removeEventListener('mousewheel', this.mouseWheel, false) // 销毁鼠标滚动事件
}
}
},
mounted() {
console.log('组件挂载')
window.addEventListener('mousewheel', this.mouseWheel)
},
destroyed() {
window.removeEventListener('mousewheel', this.mouseWheel, false) // 组件销毁时销毁鼠标滚动事件
},
methods: {
remoteMethod(query) {
if (query !== '') {
this.loading = true
setTimeout(() => {
this.loading = false
this.options = this.numberList_.filter(item => {
return item.label.toLowerCase()
.indexOf(query.toLowerCase()) > -1
}).slice(0, 10)
}, 200)
} else {
this.options = []
}
},
loadMore: function() {
this.i++
this.numberList_ = this.numberList.slice(0, 10 * this.i) // 多加载10条结果
},
input: function(searchData) {
this.remoteForm.result = searchData.target.value
this.$emit('input', searchData)
},
change: function(data) {
this.$emit('change', data)
},
focus: function() {
this.$emit('focus')
},
select: function(data) {
this.$emit('select')
},
mouseWheel: function() {
if (this.$refs.remoteSearch && this.$refs.remoteSearch.$refs && this.$refs.remoteSearch.$refs.scrollbar) {
const scrollHeight = Math.round(this.$refs.remoteSearch.$refs.scrollbar.moveY) // 滚动条滚动的距离
console.log(scrollHeight);
console.log(36 * this.i - 1);
if (scrollHeight && scrollHeight !== 0 && scrollHeight > 36 * this.i) {
this.loadMore()
}
}
}
}
}
script>
<style lang="scss">
.remote-search {
width: 100%;
.el-input__inner {
padding: 2px 5px;
}
}
.remote-search {
input::-webkit-input-placeholder {
color: #616161a1;
}
input::-moz-input-placeholder {
color: #6161618c;
}
input::-ms-input-placeholder {
color: #61616173;
}
}
.remote-tooltip {
padding: 5px!important;
}
.search-keyword {
text-align: center;
padding: 5px;
color: rgb(133, 133, 133);
font-size: 12px;
cursor: pointer;
:hover {
color: rgb(71, 159, 218);
}
}
.search-keyword-bottom {
text-align: center;
padding: 5px;
color: rgb(133, 133, 133);
font-size: 12px;
}
style>
调用:
changeResultDlgVisible 是所在表单是否显示(值为false和true) startNumber 默认回显的数据
resultNumberInput()是访问后台接口,获取查询结果
numberList是接口返回的查询结果,格式如下,须有value和label两个字段
<remote-search
:result="startNumber"
:number-list="numberList"
:placeholder="startNumber"
:has-remote="changeResultDlgVisible"
size="small"
@input="resultNumberInput(startNumber, 'paperNumber', $event.target.value)"
@change="startNumber=$event;$forceUpdate"
@focus="numberList = []"
/>
就可以啦~
上面是每次输入都会访问后台,感觉挺浪费的,可以给input输入事件添加防抖,隔段时间再访问后台,下面直接给代码:
1.创建一个js文件,存放防抖函数,我将它放在common目录下,新建文件debounce.js
/**
* 防抖函数
* @param func 用户传入的防抖函数
* @param wait 等待的时间
* @param immediate 是否立即执行
*/
export const debounce = function(func, wait = 50, immediate = false) {
console.log(func);
// 缓存一个定时器id
let timer = null
let result
const debounced = function(...args) {
// 如果已经设定过定时器了就清空上一次的定时器
if (timer) {
clearTimeout(timer)
}
if (immediate) {
const callNow = !timer
// 等待wait的时间间隔后,timer为null的时候,函数才可以继续执行
timer = setTimeout(() => {
timer = null
result = func.apply(this, args)
}, wait)
// 未执行过,执行
if (callNow) result = func.apply(this, args)
} else {
// 开始一个定时器,延迟执行用户传入的方法
timer = setTimeout(() => {
// 将实际的this和参数传入用户实际调用的函数
func.apply(this, args)
}, wait)
}
return result
}
debounced.cancel = function() {
clearTimeout(timer)
timer = null
}
// 这里返回的函数时每次用户实际调用的防抖函数
return debounced
}
2.引入
import { debounce } from '@/common/debounce'
3.使用到input事件上
input: debounce(function(searchData) {
const _this = this
_this .remoteForm.result = searchData.target.value
_this .$emit('input', searchData)
}, 500, false),
```