虚拟列表实现

通过只渲染可见区域来优化长列表性能

问题

如何实现虚拟列表来优化长列表的渲染性能?

解答

虚拟列表只渲染可见区域内的列表项,而不是渲染整个列表,从而大幅降低页面渲染复杂度。

实现步骤

1. 计算可见区域

根据容器高度和列表项高度,计算当前可见区域内的列表项数量和起始位置。

const containerHeight = 600; // 容器高度
const itemHeight = 50; // 每项高度
const visibleCount = Math.ceil(containerHeight / itemHeight); // 可见项数量
const startIndex = Math.floor(scrollTop / itemHeight); // 起始索引
const endIndex = startIndex + visibleCount; // 结束索引

2. 渲染可见区域

只渲染计算出的可见区域内的列表项。

function VirtualList({ data, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);
  
  const visibleCount = Math.ceil(containerHeight / itemHeight);
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(startIndex + visibleCount, data.length);
  const visibleData = data.slice(startIndex, endIndex);
  
  return (
    <div 
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={(e) => setScrollTop(e.target.scrollTop)}
    >
      <div style={{ height: data.length * itemHeight, position: 'relative' }}>
        <div style={{ transform: `translateY(${startIndex * itemHeight}px)` }}>
          {visibleData.map((item, index) => (
            <div key={startIndex + index} style={{ height: itemHeight }}>
              {item}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

3. 动态调整列表高度

设置占位容器的总高度,确保滚动条正确显示。

const totalHeight = data.length * itemHeight;
// 占位容器高度为总高度,内部只渲染可见项

4. 滚动时动态加载

监听滚动事件,根据滚动位置更新可见区域的列表项。

const handleScroll = (e) => {
  const scrollTop = e.target.scrollTop;
  setScrollTop(scrollTop);
  // 根据新的 scrollTop 重新计算并渲染可见项
};

性能优化技巧

  • 缓冲区:在可见区域上下各增加几项作为缓冲,减少滚动时的白屏
  • 节流:对滚动事件进行节流处理,避免频繁计算
  • 缓存:缓存已渲染的列表项,复用 DOM 节点
  • 预加载:根据滚动方向和速度预测用户行为,提前加载数据

关键点

  • 只渲染可见区域内的列表项,通过 scrollTopitemHeight 计算起始和结束索引
  • 使用占位容器撑开总高度,保证滚动条正确显示
  • 通过 transform: translateY() 定位可见项到正确位置
  • 监听滚动事件动态更新可见区域,配合节流优化性能
  • 添加上下缓冲区可以减少滚动时的白屏现象