前端虚拟列表实现:避免一次性渲染大量 DOM 节点
背景与问题
前端开发中,处理长列表数据时若一次性渲染所有项(如 10000 个),会导致页面卡顿甚至崩溃。例如一个下拉列表有 5000 个选项全部渲染,会严重影响性能。
反面教材
// 反面教材:一次性渲染所有数据
function BigList({ items }) {
return (
<ul style={{ height: '400px', overflow: 'auto' }}>
{items.map(item => (
<li key={item.id} style={{ height: '50px' }}>
{item.name}
</li>
))}
</ul>
);
}
// 使用
<BigList items={Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` }))} />
渲染 10000 个 DOM 节点会消耗大量内存和计算资源,导致浏览器响应迟缓。
实现方案
1. 基础虚拟列表实现
// 正确姿势:基础虚拟列表
import { useState, useRef, useMemo, useCallback } from 'react';
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef(null);
// 计算可见区域
const visibleCount = Math.ceil(containerHeight / itemHeight);
const totalHeight = items.length * itemHeight;
// 计算起始和结束索引
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + visibleCount + 1, items.length);
// 获取可见项
const visibleItems = useMemo(() => {
return items.slice(startIndex, endIndex);
}, [items, startIndex, endIndex]);
// 偏移量
const offsetY = startIndex * itemHeight;
const handleScroll = useCallback((e) => {
setScrollTop(e.target.scrollTop);
}, []);
return (
<div ref={containerRef} style={{ height: containerHeight, overflow: 'auto' }} onScroll={handleScroll}>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<div key={item.id} style={{ height: itemHeight }} className="virtual-list-item">
{item.name}
</div>
))}
</div>
</div>
</div>
);
}
// 使用
function App() {
const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` }));
return <VirtualList items={items} itemHeight={50} containerHeight={400} />;
}
2. 使用 react-window
// 正确姿势:使用 react-window
import { FixedSizeList as List } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style} className="list-item">
{items[index].name}
</div>
);
return (
<List height={400} itemCount={items.length} itemSize={50}>
{Row}
</List>
);
}
// 使用
function App() {
const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` }));
return <VirtualList items={items} />;
}
3. 使用 vue-virtual-scroller
<!-- Vue3 中使用 vue-virtual-scroller -->
<template>
<RecycleScroller :items="items" :item-size="50" key-field="id" v-slot="{ item }">
<div>{{ item.name }}</div>
</RecycleScroller>
</template>
<script setup>
import { RecycleScroller } from 'vue-virtual-scroller';
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
const items = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` }));
</script>
<style>
.scroller { height: 400px; }
.item { height: 50px; display: flex; align-items: center; padding: 0 20px; }
</style>
核心原理与最佳实践
1. 虚拟列表原理
- 只渲染可见项:仅渲染视口内的元素,减少 DOM 数量。
- 计算偏移量:通过 CSS transform 移动内容位置。
- 动态高度支持:预估高度或动态计算以适应不同内容。
2. 最佳实践
- 固定高度:优先使用固定高度,性能表现更好。
- 缓冲区域:上下多渲染几个 item,避免滚动时的白屏现象。
- 滚动优化:结合 requestAnimationFrame 优化滚动事件处理。
总结
虚拟列表是性能优化的重要手段,而非炫技。通过虚拟化技术,应用无需渲染所有数据即可流畅展示。建议根据项目需求选择合适的库或自行实现,以提升用户体验。


