一、为什么要用虚拟列表?
问题本质
当列表数据很多时(如 1w+):
-
DOM 数量过多 → 重排 / 重绘严重
-
滚动卡顿
-
React diff 变慢
-
表格(Antd Table)尤其明显
👉 瓶颈不在 JS,而在 DOM
二、虚拟列表核心原理(一句话版)
只渲染"可视区域 + 上下缓冲"的那一小段数据,其它用空白高度"占位"
关键点只有 4 个:
-
滚动容器高度固定
-
每一项高度固定 or 可计算
-
根据 scrollTop 计算 startIndex / endIndex
-
用 translateY 或 padding-top 做偏移
三、基础实现原理(固定高度版)
1️⃣ 核心计算公式
const itemHeight = 40
const containerHeight = 400
const visibleCount = Math.ceil(containerHeight / itemHeight)
const startIndex = Math.floor(scrollTop / itemHeight)
const endIndex = startIndex + visibleCount + buffer
2️⃣ DOM 结构示意
<div class="container" onScroll>
<div style="height: totalHeight">
<div style="transform: translateY(offset)">
{visibleItems.map(render)}
</div>
</div>
</div>
3️⃣ React 简化示例
const VirtualList = ({ data }) => {
const containerRef = useRef(null)
const [scrollTop, setScrollTop] = useState(0)
const itemHeight = 40
const containerHeight = 400
const buffer = 5
const startIndex = Math.floor(scrollTop / itemHeight)
const visibleCount = Math.ceil(containerHeight / itemHeight)
const endIndex = startIndex + visibleCount + buffer
const visibleData = data.slice(startIndex, endIndex)
return (
<div
ref={containerRef}
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
>
<div style={{ height: data.length * itemHeight }}>
<div style={{ transform: `translateY(${startIndex * itemHeight}px)` }}>
{visibleData.map(item => (
<div key={item.id} style={{ height: itemHeight }}>
{item.name}
</div>
))}
</div>
</div>
</div>
)
}
✅ DOM 永远只有几十个
四、进阶:不定高虚拟列表(真实项目常见)
难点
-
表格内容不确定
-
操作列 / 多行文本 / 自动换行
解决思路
方案一(推荐):高度缓存 + 二分查找
const heightMap = new Map<index, height>()
-
首次渲染测量高度
-
缓存起来
-
用累计高度数组做滚动定位
👉 react-window、react-virtual 都是这个思路
方案二:估算高度 + 滚动修正
-
先用
estimatedHeight -
渲染后真实测量
-
修正
scrollTop
五、直接用成熟库(强烈推荐)
1️⃣ react-window(轻量,首选)
npm i react-window
import { FixedSizeList as List } from 'react-window'
<List
height={600}
itemCount={10000}
itemSize={40}
width="100%"
>
{({ index, style }) => (
<div style={style}>{data[index].name}</div>
)}
</List>
👉 性能极好、API 简单
2️⃣ react-virtual(TanStack)
-
不定高
-
可横向虚拟
-
表格/瀑布流都行
const rowVirtualizer = useVirtualizer({
count: data.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 40,
})
3️⃣ Ant Design 表格怎么办?
官方推荐组合
-
Table + react-window -
VirtualTable(社区实现)
Antd v5 已支持 virtual:
<Table
columns={columns}
dataSource={data}
scroll={{ y: 600 }}
virtual
/>
⚠️ 列宽、固定列、合并单元格要测试
六、你在性能平台里如何用(结合你实际项目)
你之前提到的场景非常典型:
-
性能查询列表
-
性能问题 / 覆盖度表格
-
CPU 架构对比
推荐策略
| 场景 | 建议 |
|---|---|
| 普通列表 | react-window |
| Antd 表格 | Antd v5 virtual 或 react-virtual |
| 图表下方明细 | 虚拟列表 + memo |
| 操作列多 | 行高度固定,避免 auto |
七、常见坑(非常重要)
❌ 1. 监听 window.scroll
👉 必须用容器滚动
❌ 2. key 用 index
👉 用业务唯一 id
❌ 3. 频繁 setState
👉 requestAnimationFrame / 节流
❌ 4. 虚拟 + 动画
👉 几乎一定卡
八、性能组合拳(虚拟列表 ≠ 万能)
虚拟列表 必须配合:
-
React.memo -
useCallback -
列 render 函数拆分
-
避免匿名函数
-
避免同步计算
九、总结一句话
虚拟列表的本质不是"快",而是"DOM 数量永远可控"