综述
虚拟列表是一种优化技术,专注于只渲染可视区域的数据,避免大量数据加载导致页面卡顿。
本文将通过一个简单易用的 useVirtualList Hook 来实现这个功能,并提供多个实际应用场景和示例代码,帮助开发者在 Next.js 中高效处理海量数据的渲染。
背景问题
在某个项目中,遇到以下问题:
- 数据量大:接口返回了几万条数据,没有分页。
- 页面表现:渲染列表时非常卡顿,点击交互也很慢。
为了解决这一问题,从分页加载方案转向虚拟列表。通过只渲染可视区域的数据,减少 DOM 元素的渲染,最终大幅提升页面性能。
什么是虚拟列表?
虚拟列表是一种优化渲染性能的技术,核心思想是:
- 只渲染可视区域的数据,其他数据用占位符模拟。
- 动态计算渲染内容,用户滚动时更新可视区域的内容。
应用场景:
- 数据量较大的表格(如库存管理系统)。
- 列表类组件(如聊天记录、文件目录)。
- 选择器(如下拉菜单中有上千选项)。
核心实现
1. 滚动模拟
通过设置容器的 height 或 max-height 实现滚动。利用虚拟高度模拟数据总量,从而触发滚动条。
2. 渲染正确的数据
根据滚动位置计算出当前需要渲染的数据索引范围,只渲染这些数据。
3. 渲染到可视区域
通过 transform: translateY 将数据偏移到可视区域,避免超出区域的元素被渲染。
定高虚拟列表实现
Hook 参数配置
以下是 useVirtualList 的配置参数:
data: 数据源(响应式)。itemHeight: 每个列表项的固定高度。size: 每次渲染的数据量。scrollContainer: 滚动容器的选择器。actualHeightContainer: 用于撑开高度的容器选择器。translateContainer: 偏移容器的选择器。
Hook 核心代码
import { useState, useEffect, useMemo } from "react";
function useVirtualList(config) {
const [startIndex, setStartIndex] = useState(0);
const [endIndex, setEndIndex] = useState(config.size - 1);
const actualRenderData = useMemo(() => {
return config.data.slice(startIndex, endIndex + 1);
}, [config.data, startIndex, endIndex]);
useEffect(() => {
const scrollContainer = document.querySelector(config.scrollContainer);
const actualHeightContainer = document.querySelector(config.actualHeightContainer);
const translateContainer = document.querySelector(config.translateContainer);
actualHeightContainer.style.height = `${config.data.length * config.itemHeight}px`;
const handleScroll = (e) => {
const scrollTop = e.target.scrollTop;
translateContainer.style.transform = `translateY(${scrollTop}px)`;
setStartIndex(Math.floor(scrollTop / config.itemHeight));
setEndIndex(startIndex + config.size - 1);
};
scrollContainer.addEventListener("scroll", handleScroll);
return () => {
scrollContainer.removeEventListener("scroll", handleScroll);
};
}, [startIndex, endIndex, config]);
return { actualRenderData };
}
示例代码
1. 普通列表
<ul className="scroll-container">
<div className="actual-height-container">
<div className="translate-container">
{actualRenderData.map((item) => (
<li key={item.id}>
{item.text}
</li>
))}
</div>
</div>
</ul>
配置调用:
const { actualRenderData } = useVirtualList({
data: tableData,
itemHeight: 50,
size: 20,
scrollContainer: ".scroll-container",
actualHeightContainer: ".actual-height-container",
translateContainer: ".translate-container",
});
2. Next.js + Ant Design 表格
const { actualRenderData } = useVirtualList({
data: tableData,
itemHeight: 50,
size: 10,
scrollContainer: ".ant-table-body",
actualHeightContainer: ".ant-table-content",
translateContainer: ".ant-table-tbody",
});
不定高虚拟列表
对于列表项高度不固定的场景,需要动态计算每个列表项的实际高度并缓存。核心技术点:
nextTick:等待 DOM 更新后计算实际高度。- 高度缓存:通过缓存每个列表项的高度,避免重复计算。
- 滚动计算:根据滚动高度和缓存动态定位需要渲染的列表项。
总结
通过虚拟列表优化,页面能够轻松应对海量数据的渲染任务。无论是定高还是不定高场景,useVirtualList Hook 都能显著提升性能。
实际应用:
- 在线电商后台快速浏览大量商品。
- 聊天应用中流畅加载历史消息。
- 数据分析系统中的报表渲染。
效果
虚拟列表能够处理 10w 条数据,保持丝滑滚动,为前端性能优化提供了一种简单而高效的解决方案。