场景
当我们使用 Select 选择器存放大量数据的时候。 会发现存在这么2个问题。 1.接口响应时间较长。(因为数据量较多,一次查询的所有)甚至有可能超时。 2.前端下拉框滑动卡顿。 这个时候们如何解决上面面临的问题呢? 有的小伙伴可能会说: 1.分页加载。确实是可以解决问题。 2.页面卡顿使用虚拟dom.超时喊后端自己优化代码 因为项目中使用的是 element-ui,没有虚拟加载。 不想 Ant Design Vue一样,有virtual属性,设置 true可以开启虚拟滚动。 这里就感觉到 Ant Design Vue处理的比element-ui好一些(希望各位大佬不要喷我) 所以我们只能选择异步加载。 当页面滑动到底部的时候,加载下一页的数据
下拉框滑动到底部触发事件,加载下一页的数据
我们首先需要做到的是:获取下拉框元素的DOM节点。 由于一个页面可能有多个dom节点。 我们需要使用 popper-class属性来设置 Select 下拉框的类名。 然后后通过dom.scrollHeight - dom.scrollTop <= dom.clientHeight 来判断是否触底了。下面我们就来实现了一下
// 子组件 <template> <div> <el-select popper-class="more-next-box" v-model="value" placeholder="请选择"> <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> </div> </template> <script> export default { data() { return { options: [{ value: '001', label: '数据1' }, { value: '002', label: '数据2' }, { value: '003', label: '数据3' }, { value: '004', label: '数据4' }], value: '' } }, mounted() { // 获取dom节点 const domElementNode = document.querySelector('.more-next-box .el-select-dropdown__wrap') // 注册下拉滚动事件 domElementNode.addEventListener('scroll', ()=>{ const isBottom = domElementNode.scrollHeight - domElementNode.scrollTop <= domElementNode.clientHeight if (isBottom) { console.log('是否到底了') } }) }, } </script>
现在我们已经成功判断是否下拉到底了。 接下来我们应该去封装一下这个组件的属性和事件 争取拥有官方文档中的所有属性和事件
如何让 el-select拥有官方的所有属性呢?
我们可以使用props属性,一个一个的写上去。 这个操作虽然可以但是官方文档多20+多个属性。 是不是太麻烦了呀? 我们可以使用 $attrs配合inheritAttrs: false 来解决这个问题 $attrs里面包含着父组件传递的所有数据(除style和class)。 当一个组件声明了prop时候,attrs里面包含除去prop里面的数据剩下的数据。 而inheritAttrs: false,表示将属性不设置在根元素上,而是设置在指定的元素上
//组件优化为 <template> <div> <el-select v-bind="$attrs" v-model="value" > <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> </div> </template> //表示不添加在组件的根元素上 inheritAttrs: false,
//页面使用 <SelectLoadMore popper-class="more-next-box" :clearable="true" placeholder="请选择数据"> </SelectLoadMore>
如何让 el-select拥有官方的所有事件呢?
$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。
//组件再次优化为 <el-select v-on="$listeners" v-bind="$attrs" v-model="value" > <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select>
//页面使用 <div> <SelectLoadMore popper-class="more-next-box" :clearable="true" @visible-change="visibleChange" placeholder="请选择数据" @change="changeHandler" ></SelectLoadMore> </div>
el-option中key,label,value这些字段不一样怎么处理?
这个我们直接使用props来处理就好了。 这样我们就需要担心key键名不一致这个问题了
<div> <el-select v-on="$listeners" v-bind="$attrs" v-model="value" > <el-option v-for="item in options" :key="item[setKey]" :label="item[setLabel]" :value="item[setValue]"> </el-option> </el-select> </div> props: { setKey: { type: String, default:'key' }, setLabel: { type: String, default: 'label' }, setValue: { type: String, default: 'value' } },
下拉到底部加载下一页的数据
// 组件 created() { this.getList() }, mounted() { // 获取dom节点 const domElementNode = document.querySelector('.more-next-box .el-select-dropdown__wrap') // 注册下拉滚动事件 domElementNode.addEventListener('scroll', ()=>{ const isBottom = domElementNode.scrollHeight - domElementNode.scrollTop <= domElementNode.clientHeight if (isBottom) { console.log('是否到底了') // 这里应该还有一个判断,总条数和当前列表中的数据做一个比较。 // 这里我就不写了 this.getList(this.pageSize += 1) } }) }, methods: { // 请求的数据 getList(index = 1) { let arr = [] setTimeout(() => { for (let i = 0; i < 10; i++){ arr.push({ value: (index-1) *10 + i, label: '数据'+ (index - 1) * 10 + i }) } this.options=this.options.concat(arr) },2500) } },
加载下一页的时候做一个加载提示
从上面这张图中,我们可以看见。 加载的时候没有任何的提示和效果。非常的不友好。 用户都不知道是在加载数据。我们现在优化一下
//子组件 <el-select v-on="$listeners" v-bind="$attrs" v-model="value" > <el-option v-for="item in options" :key="item[setKey]" :label="item[setLabel]" :value="item[setValue]"> </el-option> <el-option v-show="loadingFlag" label="正在努力加载中" value="xx01"></el-option> </el-select> data() { return { options: [], value: '', pageSize: 1, loadingFlag:false //控制加载显示的 } }, getList(index = 1) { this.loadingFlag=true //开启加载中 let arr = [] setTimeout(() => { for (let i = 0; i < 10; i++){ arr.push({ value: (index-1) *10 + i+'', label: '数据'+ (index - 1) * 10 + i }) } this.options = this.options.concat(arr) this.loadingFlag = false //加载结束 },2500) }
md发现了一个bug,用户选择‘正在努力加载中’中这一项怎么办?
有细心的小伙伴,可能会说: <el-option v-show="loadingFlag" label="正在努力加载中" value="xx01"></el-option> 这样写真的没有问题吗? 万一用户选择了,这一项数据怎么办? 这,这,这,这,这恐怕就会100%出现bug了。 使用 disabled 来处理禁止选择这一项。 哈哈··,我简直是一个小天才(手动狗头)
使用 disabled 来处理禁止选择这一项
<el-select v-on="$listeners" v-bind="$attrs" v-model="value" > <el-option v-for="item in options" :key="item[setKey]" :label="item[setLabel]" :value="item[setValue]"> </el-option> <el-option disabled v-show="loadingFlag" label="正在努力加载中" value="xx01"></el-option> </el-select>
继续优化加载动画
有的小伙伴说,这个虽然不能选择,也有加载提示。 但是是灰色的,不太友好。 你他娘的,就你屁事多。烦死了(悟空表情)。 <el-select v-on="$listeners" v-bind="$attrs" v-model="value" > <el-option v-for="item in options" :key="item[setKey]" :label="item[setLabel]" :value="item[setValue]"> </el-option> <p v-show="loadingFlag">正在努力加载中</p> </el-select> 我们可以自己用一个标签来处理动画,这里我就不在做动画了。 其实如果你觉得 disabled的样式也可以修改的,
其实中的disabled也是可以修改样式的
<el-option disabled v-show="loadingFlag" label="正在努力加载中" value="xx01"></el-option> <style lang="scss" scoped> .el-select-dropdown__item.is-disabled{ color: red; } </style>
如何做数据回填
有些时候,我们除了需要选择值,还需要回填值。 我们可以使用 props来传递值,然后在created中赋值。 // 数据回填的值 writeData: { type: String, default: '' } created() { if (this.writeData) { this.value = this.writeData } this.getList() },
全部代码
// 使用的页面 <template> <div> <SelectLoadMore popper-class="more-next-box" :clearable="true" :writeData="writeData" @visible-change="visibleChange" placeholder="请选择数据" @change="changeHandler" ></SelectLoadMore> </div> </template> <script> import SelectLoadMore from "@/components/SelectLoadMore.vue" export default { components: { SelectLoadMore }, data() { return { writeData: 108 } }, methods: { changeHandler() { console.log('值发生了改变') }, visibleChange(flag){ console.log('flag', flag) } }, } </script>
组件 <template> <div> {{ value }} <el-select v-on="$listeners" v-bind="$attrs" v-model="value" > <el-option v-for="item in options" :key="item[setKey]" :label="item[setLabel]" :value="item[setValue]"> </el-option> <!-- <p v-show="loadingFlag">正在努力加载中</p> --> <el-option disabled v-show="loadingFlag" label="正在努力加载中" value="xx01"></el-option> </el-select> </div> </template> <script> export default { //表示不添加在组件的根元素上 inheritAttrs: false, props: { setKey: { type: String, default:'key' }, setLabel: { type: String, default: 'label' }, setValue: { type: String, default: 'value' }, // 数据回填的值 writeData: { type: String, default: '' } }, data() { return { options: [], value: '', pageSize: 1, loadingFlag:true } }, created() { if (this.writeData) { this.value = this.writeData } this.getList() }, mounted() { // 获取dom节点 const domElementNode = document.querySelector('.more-next-box .el-select-dropdown__wrap') // 注册下拉滚动事件 domElementNode.addEventListener('scroll', ()=>{ const isBottom = domElementNode.scrollHeight - domElementNode.scrollTop <= domElementNode.clientHeight if (isBottom) { console.log('是否到底了') // 这里应该还有一个判断,总条数和当前列表中的数据做一个比较。 // 这里我就不写了 this.getList(this.pageSize += 1) } }) }, methods: { getList(index = 1) { this.loadingFlag=true let arr = [] console.log('index', index) setTimeout(() => { for (let i = 0; i < 10; i++){ arr.push({ value: (index-1) *10 + i+'', label: '数据'+ (index - 1) * 10 + i }) } this.options = this.options.concat(arr) // this.loadingFlag = false },1500) } }, } </script> <style lang="scss" scoped> .el-select-dropdown__item.is-disabled{ color: red; } </style>
尾声
如果你觉得,这篇文章写的不错,对你有用。 请给我点一个赞,感谢了。