为什么要有虚拟列表这个东西
很多时候前端被迫被逼着接收上百条,上千条,上万条数据(因为需求的缘故),没有相关经验的前端会直接直接赋值渲染上去。 但是这样子操作是有很大的弊端的。轻则让浏览器卡顿,重则浏览器崩溃。更严重的老板直接过来骂人。
也就是说,我们不能一股脑的直接赋值,要有合理的方案。
- 后端给出分页 (是最好的处理方式 有什么东西是不能让后端给前端压力的呢)
- 前端将数据切成小块 进行分页 (但是可能业务被迫需要更加直观的展示不让使用分页)
- 前端通过搜索去过滤数据再渲染 (可以存入到缓存里,读取缓存内的数据,但是会有过滤后还是有大量数据的问题,和缓存有大小问题等限制,是不合理的方案)
- 虚拟列表 (通过滚动条只渲染可见部分,随着滚动加载最新的数据,非常合理的方式)
通过以上解释,虚拟列表是最佳合理的方案,实例:
<template>
<div id="app">
<div
class="virtual-list-container"
ref="listContainerRef"
@scroll="handleScroll"
>
<div class="list-phantom">
<div
class="list-item"
v-for="(item, index) in state.visibleItem"
:key="index"
>
虚拟化列表数据 {{ item.name }}
</div>
</div>
</div>
</div>
</template>
<script lang="tsx" setup>
import { ref, reactive, onMounted } from "vue";
let state = reactive({
visibleItem: [] as any,
visibleItemCount: 20, // 可见区域显示多少条
count: 800000, // 需要生成的数据总条数
});
// 数据生成函数
const genertteItems = (count: number): any[] => {
const items: any = [];
Array.from({ length: count }).forEach((item, index: number) => {
items.push({ id: index, name: `${index}名称` });
});
return items;
};
// 关键代码
// 通过ref获取带有滚动条的dom
const listContainerRef = ref<any>(null);
// 滚动事件
const handleScroll = () => {
// 滚动容器
const container = listContainerRef.value;
// 获取容器当前至容器顶部距离多远
const scrollTop = container?.scrollTop || 0;
// 获取容器可视区域总高度
const scrollHeight = container?.scrollHeight || 0;
// 获取容器的总高度
const containerHeight = container?.clientHeight || 0;
// 如果滚动距离加上可是高度大于总高度 说明到了容器底部
if (scrollTop + containerHeight >= scrollHeight) {
// 滚动到底部加载更多数据
loadMoreData();
}
};
// 加载更多函数
const loadMoreData = () => {
const startIndex = state.visibleItem.length;
const endIndex = startIndex + state.visibleItemCount;
const newItems = genertteItems(state.count).slice(startIndex, endIndex);
state.visibleItem = [...state.visibleItem, ...newItems];
};
onMounted(() => {
handleScroll(); // 确保组件挂载后初始化一次数据\
});
</script>
<style lang="less">
.list-item {
height: 80px;
border: 1px solid red;
}
.virtual-list-container {
overflow-y: auto;
height: 400px; /* 容器高度 */
position: relative;
}
.list-phantom {
position: absolute;
width: 100%;
pointer-events: none;
}
</style>
实现效果