前端如何虚拟列表优化?

一、为什么要用虚拟列表?

问题本质

当列表数据很多时(如 1w+):

  • DOM 数量过多 → 重排 / 重绘严重

  • 滚动卡顿

  • React diff 变慢

  • 表格(Antd Table)尤其明显

👉 瓶颈不在 JS,而在 DOM


二、虚拟列表核心原理(一句话版)

只渲染"可视区域 + 上下缓冲"的那一小段数据,其它用空白高度"占位"

关键点只有 4 个:

  1. 滚动容器高度固定

  2. 每一项高度固定 or 可计算

  3. 根据 scrollTop 计算 startIndex / endIndex

  4. 用 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 都是这个思路


方案二:估算高度 + 滚动修正
  1. 先用 estimatedHeight

  2. 渲染后真实测量

  3. 修正 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 数量永远可控"

相关推荐
EF@蛐蛐堂几秒前
【vue】新前端工具链Vite+ Alpha
前端·javascript·vue.js
小木木爸3 分钟前
若依框架图片预览异常:Content-Type变成application/octet-stream,前端后端谁的锅?
前端·状态模式
爱学习的程序媛15 分钟前
【Web前端】蚂蚁AntV:企业级数据可视化全栈方案
前端·信息可视化·前端框架·web·数据可视化
文心快码BaiduComate21 分钟前
Comate Spec Mode能力升级:让复杂任务开发更可控、更稳定
前端·后端
前端付豪25 分钟前
实现 AI 回复支持 Markdown 渲染
前端·人工智能·markdown
阳火锅36 分钟前
鳌虾 AoCode:重新定义 AI 编程助手的下一代可视化工具
前端·人工智能·架构
拾贰_C39 分钟前
【node】node彻底卸载删除
前端
SuperEugene40 分钟前
Vue3 组合式函数(Hooks)封装规范实战:命名 / 输入输出 / 复用边界 + 避坑|Vue 组件与模板规范篇
开发语言·前端·javascript·vue.js·前端框架
芝士麻雀43 分钟前
掌握 .claude/ 目录:让 Claude Code 真正懂你的项目
前端·后端
cmd44 分钟前
JS深浅拷贝全解析|常用方法+手写实现+避坑指南(附完整代码)
前端·javascript