基于ant组件库挑选框组件-封装滚动刷新的分页挑选框
目录
- 一,功能介绍
- 二,思路介绍
- 三,关键点
- 1.触底条件
- 2.判断什么时候发起请求
- 四,组件参数及解释
- 五,整体代码
- 后记
一,功能介绍
本文是基于ant组件库的select组件进行封装:
ant组件库选择器Select组件
在开发中,由于挑选框数据过多,一次加载全部数据展示在前端可能会导致数据卡顿,卡顿的主要原因有两个:第一是向后端请求大量数据,这个过程需要时间;第二是前端如果一次性渲染多个dom会导致页面卡顿。
研究了ant和element组件库后发现,其实它们的下拉框都有做虚拟列表,一次性只渲染十个dom,因此在组件库的基础上封装,我们只需要解决第一个问题,需要后端提供分页接口,前端展示固定条数据,再次下划后调用分页接口。再利用其组件库自带的虚拟列表来优化性能。
二,思路介绍
这里是基于ant组件库,但是放到element上思路其实类似。
ant组件库的下拉框,一条数据的高度大概是32px,我们也可也根据自己设置的样式高度来自定义。这里为什么要提到高度,因为是根据当前渲染的高度来判断目前到了第几条数据。假如我们一次性展示10条数据(展示页数尺寸是10条一页),那么高度触底为大概320px的时候我们需要调用接口请求下一页的数据。
这里有一个问题,每一次请求,分页的长度为多少?我封装的是11,设想第一次请求,一共11条数据,展示10条,想要加载全部需要下划滚动,所以才有可能触发下拉这个方法。如果设置一条展示10条,且分页请求只有10条的话,第一次加载时刚好占满盒子高度,无法触发分页请求。
如果需要搜索功能,可以在分页封装完之后,写搜索查询的事件。只需要加上对应字段就好,逻辑也是遵循刚写的分页逻辑的。
三,关键点
1.触底条件
用到的是组件库的这个方法:
触底条件:
如果:滚动距离 + 元素的垂直高度 === 容器高度(滚动到底部)则为触底。
2.判断什么时候发起请求
如果触底,且当前长度 < 数据总长度,则发起请求。为什么是size+1,在上一节已经给出了解释。
四,组件参数及解释
组件的封装在于复用,使用组件,需要传进去的有代表label和value的字段,请求函数,每一页的长度(分页长度)以及搜索字段。
传进去的如果是10,展示10条,也就是320px左右(如果有改样式需要自行定义32,这个值也可也考虑作为参数);但是实际上发起请求是11条,因为要产生滚动的触发条件。
五,整体代码
<script setup lang="ts">
import { selectProps } from 'ant-design-vue/es/vc-select';
// 该组件的作用是下拉框下拉更新,用于分页接口的下拉框,仅针对不修改样式的ant下拉框
const props = defineProps({
...selectProps,
width: {
type: Number,
default: 200,
},
// label字段
label: {
type: String,
default: '',
},
// value字段
value: {
type: String,
default: '',
},
// 分页请求
request: {
type: Function,
default: () => {
return {
data: '',
success: false,
};
},
},
// 每页长度(展示多少条数据后下拉更新第二页)
size: {
type: Number,
default: 10,
},
// 搜索字段
searchName: {
type: String,
default: '',
},
});
// 返回值-可有可无
const resultValue = defineModel('resultValue', {
type: String,
default: '',
});
// 下拉展示数据
const selectOption = ref([]);
// 总数据长度
const selectTotal = ref(0);
// 获取下拉框列表信息
const getSelectOptions = async (current, size, searchVal?) => {
const { data } = await props.request({
current: current || '1',
size: size || props.size + 1,
keyword: searchVal ?? '',
});
return data;
};
// 初始状态至少要多请求一个,因为加入有大于size的数据,只展示size个,就不会有滚动条
// 想要有滚动条数据的量要大于展示量
onMounted(async () => {
const data = await getSelectOptions(1, props.size + 1);
// 处理data
selectOption.value = data.records.map((item) => {
return {
label: item[props.label],
value: item[props.value],
};
});
// 总长度
selectTotal.value = data.total;
});
// 找个值来接收搜索框文本
const searchVal = ref('');
const popup = async (e) => {
const { target } = e;
// 如果触底
if (target.scrollTop + target.offsetHeight === target.scrollHeight) {
// 先做一个判断
if (selectOption.value.length < selectTotal.value) {
// 如果数据还没有加载出来,请求当前页的后一页
const data = await getSelectOptions(
selectOption.value.length / (props.size + 1) + 1,
props.size + 1,
searchVal.value ?? '',
);
const selectOptionAdd = data.records.map((item) => {
return {
label: item[props.label],
value: item[props.value],
};
});
selectOption.value = [...selectOption.value, ...selectOptionAdd];
}
}
};
const searchChange = async (val) => {
searchVal.value = val;
const data = await getSelectOptions(1, props.size + 1, val);
selectOption.value = data.records.map((item) => {
return {
label: item[props.label],
value: item[props.value],
};
});
selectTotal.value = data.total;
};
</script>
<template>
<a-select
ref="select"
v-model:value="resultValue"
:options="selectOption"
:list-height="size * 32"
:filter-option="false"
:style="{ width: `${width}px` }"
@popup-scroll="popup"
@search="searchChange"
></a-select>
</template>
后记
感谢阅读,期望获取关注,博客会持续更新。