虚拟列表是一种优化长列表性能的方式,它只渲染可见视图区域内的数据,从而减少渲染的数量,提高页面性能。
原理
虚拟列表的实现方法如下:
- 计算可视区域的高度和位置:首先需要计算出可视区域的高度和位置,可以通过 DOM 元素的 offsetTop 和 offsetHeight 属性来计算。
- 计算数据总高度和每个数据项的高度:需要知道数据总共的高度和每个数据项的高度,可以通过 DOM 元素的 offsetHeight 属性来计算。
- 计算可见数据的起始索引和结束索引:根据可视区域的高度和位置,以及每个数据项的高度,可以计算出可见数据的起始索引和结束索引。
- 渲染可见数据:根据可见数据的起始索引和结束索引,从数据源中取出对应的数据,并渲染到页面上。
- 监听滚动事件:当用户滚动列表时,需要重新计算可见数据的起始索引和结束索引,并重新渲染可见数据。
虚拟列表的实现还需要注意以下几点:
- 数据项的高度:每个数据项的高度可能不一样,需要根据实际情况进行计算。
- 缓存数据:为了提高性能,可以对已渲染过的数据进行缓存,避免重复渲染。
- 可以使用两个buffer。缓存当前数据和预加载数据。
- 需要考虑滚动方向,更新缓存数据。
- 异步加载数据:当数据量比较大时,可以使用异步加载方式,分批加载数据,提高渲染性能。
示例
import React, { useState, useEffect, useRef } from "react";
interface VirtualListProps<T> {
data: T[];
itemHeight: number;
renderItem: (item: T) => React.ReactNode;
containerHeight: number;
overscanCount: number;
}
const VirtualList = <T,>({
data,
itemHeight,
renderItem,
containerHeight,
overscanCount,
}: VirtualListProps<T>) => {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef<HTMLDivElement>(null);
const start = Math.floor(scrollTop / itemHeight);
const end = Math.min(start + Math.ceil(containerHeight / itemHeight), data.length);
const visibleData = data.slice(start, end);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleScroll = () => {
setScrollTop(container.scrollTop);
};
container.addEventListener("scroll", handleScroll);
return () => {
container.removeEventListener("scroll", handleScroll);
};
}, []);
const paddingTop = start * itemHeight;
const paddingBottom = (data.length - end) * itemHeight;
const totalHeight = data.length * itemHeight;
return (
<div
ref={containerRef}
style={{ height: containerHeight, overflowY: "scroll" }}
>
<div style={{ height: totalHeight, paddingTop, paddingBottom }}>
{visibleData.map((item, index) => (
<div key={index} style={{ height: itemHeight }}>
{renderItem(item)}
</div>
))}
</div>
</div>
);
};
export const VirtualListDemo = () => {
const data = Array.from({ length: 10000 }, (_, i) => i);
const itemHeight = 50;
const renderItem = (item: any) => <div>{item}</div>;
const overscanCount = 10;
const containerHeight = 500;
return (
<VirtualList
data={data}
itemHeight={itemHeight}
renderItem={renderItem}
overscanCount={overscanCount}
containerHeight={containerHeight}
/>
);
};
export default VirtualList;