react大列表优化:虚拟列表原理

React 大列表优化中,虚拟列表(Virtual List) 是最常见、最有效的优化方案之一。

为什么需要虚拟列表

假设有 10 万条数据:

复制代码
{
  data.map(item => (
    <Row key={item.id} {...item} />
  ))
}

React 会一次性生成:

复制代码
<div>...</div>
<div>...</div>
...
(100000个节点)

问题:

  • DOM 节点过多

  • 首次渲染慢

  • 浏览器布局(Layout)和绘制(Paint)耗时

  • 滚动卡顿

  • 内存占用高

即使用户当前只能看到 20 条数据,浏览器仍然维护 10 万个 DOM。


虚拟列表核心思想

只渲染可视区域内的数据。

例如:

复制代码
总数据:100000条

当前屏幕:
----------------
第100条
第101条
...
第120条
----------------

实际上只渲染:

复制代码
100 ~ 120

共 20 个节点。

滚动时:

复制代码
向下滚动

100~120
↓

110~130

销毁旧节点,复用或创建新节点。

DOM 数量始终维持:

复制代码
20 ~ 30个

而不是:

复制代码
100000个

实现原理

假设:

复制代码
总数 = 100000

每行高度 = 50px

总高度:

复制代码
100000 * 50

= 5000000px

容器:

复制代码
<div className="container">
  <div className="phantom"></div>
  <div className="content"></div>
</div>

结构:

复制代码
container
│
├── phantom
│     高度5000000px
│
└── content
      真正渲染的数据

1. 创建占位元素(phantom)

复制代码
<div
  style={{
    height: data.length * itemHeight
  }}
/>

作用:

让滚动条看起来像真的有 10 万条数据。

复制代码
滚动条长度正常

但实际上没有渲染全部节点。


2. 监听滚动

复制代码
const handleScroll = (e) => {
  const scrollTop = e.target.scrollTop;
};

例如:

复制代码
scrollTop = 5000px

3. 计算开始索引

公式:

复制代码
startIndex = Math.floor(
  scrollTop / itemHeight
);

例如:

复制代码
5000 / 50

= 100

说明:

复制代码
当前从第100条开始显示

4. 计算结束索引

可视区域:

复制代码
containerHeight = 500px

可显示:

复制代码
500 / 50

= 10条

结束索引:

复制代码
endIndex = startIndex + visibleCount;

100 + 10

= 110

5. 截取数据

复制代码
const visibleData =
  data.slice(
    startIndex,
    endIndex
  );

只渲染:

复制代码
{
  visibleData.map(...)
}

6. 偏移内容位置

如果直接渲染:

复制代码
100~110

会出现在顶部。

需要移动到正确位置:

复制代码
offsetY =
  startIndex * itemHeight;

100 * 50

= 5000px

<div
  style={{
    transform: `translateY(${offsetY}px)`
  }}
>

最终效果:

复制代码
滚动5000px

↓
显示第100条

图解

复制代码
总高度 5000000px
┌──────────────────┐
│                  │
│ phantom          │
│                  │
└──────────────────┘

      ↑

当前滚动到这里

      ↓

translateY(5000px)

┌──────────────────┐
│ 第100条          │
│ 第101条          │
│ 第102条          │
│ ...              │
└──────────────────┘

简易实现

复制代码
function VirtualList() {
  const itemHeight = 50;
  const visibleCount = 10;

  const [start, setStart] = useState(0);

  const handleScroll = (e) => {
    const scrollTop = e.target.scrollTop;

    setStart(
      Math.floor(scrollTop / itemHeight)
    );
  };

  const visibleData = data.slice(
    start,
    start + visibleCount
  );

  return (
    <div
      style={{
        height: 500,
        overflow: "auto"
      }}
      onScroll={handleScroll}
    >
      <div
        style={{
          height:
            data.length * itemHeight,
          position: "relative"
        }}
      >
        <div
          style={{
            transform: `translateY(${
              start * itemHeight
            }px)`
          }}
        >
          {visibleData.map(item => (
            <div
              key={item.id}
              style={{
                height: itemHeight
              }}
            >
              {item.name}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

优化:缓冲区(Buffer)

实际项目不会只渲染可视区域。

例如:

复制代码
可视区域:10条

上缓冲:5条
下缓冲:5条

实际渲染:

复制代码
20条

这样滚动时不会频繁白屏。

复制代码
const buffer = 5;

start =
  Math.max(
    0,
    startIndex - buffer
  );

end =
  startIndex +
  visibleCount +
  buffer;

动态高度怎么办?

上面算法要求:

复制代码
固定高度

如果每一项高度不同:

复制代码
50px
80px
120px
60px
...

就不能简单用:

复制代码
scrollTop / itemHeight

需要维护:

复制代码
高度缓存表

例如:

复制代码
[
  50,
  130,
  250,
  370,
  ...
]

通过二分查找定位:

复制代码
scrollTop
↓
对应哪一项

这就是很多虚拟列表库复杂的地方。


React常用虚拟列表库

1. react-window 官网

作者:Brian Vaughn

特点:

  • 轻量

  • 性能好

  • 推荐

    npm install react-window


2. react-virtualized 官网

特点:

  • 功能最全

  • 支持表格

  • 支持动态高度

缺点:

  • 包较大

3. TanStack Virtual 官网

特点:

  • React/Vue/Solid 通用

  • 现代项目使用较多


面试回答模板

当面试官问:

React 大列表如何优化?

可以这样回答:

大列表的性能瓶颈主要在于 DOM 数量过多导致的渲染、重排和内存开销。常见方案是使用虚拟列表。虚拟列表的核心思想是只渲染当前可视区域的数据,而不是全部数据。通过监听 scroll 事件,根据 scrollTop 计算 startIndex 和 endIndex,仅渲染这一段数据,同时利用一个等高占位元素撑开滚动高度,并通过 translateY 将当前渲染内容移动到正确位置。这样无论数据量是 1 万条还是 10 万条,页面中实际 DOM 数量都保持在几十个,从而大幅提升性能。

相关推荐
星栈1 小时前
一套 Rust 代码跑三端:为什么我开始关注 Dioxus
前端·rust·前端框架
lichenyang4531 小时前
从两个 demo 说起:WebSocket 和 SSE 到底差在哪?
前端
如烟花的信页1 小时前
外贸*登录逆向分析
javascript·爬虫·python·js逆向
前端小端长1 小时前
AI时代前端的出路在哪里?
前端·ai·职业发展
四六的六1 小时前
WebView里跑RAG——浏览器内知识检索增强实战
前端·实战·个人开发·webview·ai大模型·rag·webview内嵌开发
wanger612 小时前
Vue学习笔记
前端·javascript·vue.js
杨先生哦2 小时前
【2026热端攻防系列 3/12】反射型&存储型XSS全解:AI批量免杀、WAF绕过与企业级防御
前端·人工智能·笔记·web安全·xss
问心无愧05132 小时前
ctf show web入门123
android·前端·笔记
大刚测试开发实战2 小时前
TestHub数据工厂发布!附更新指南
前端·后端·github