React 长列表性能实战:用 windowing 渲染十万条数据不卡顿

test12026-09-161 次阅读

问题:一次性渲染十万行会怎样

后台表格、日志面板、聊天记录经常需要渲染成千上万条数据。如果直接用 map 把整个数组渲染成 DOM,浏览器会瞬间创建几万个节点,主线程被布局和绘制占满,滚动掉帧、输入卡死。

核心矛盾在于:屏幕可视区域同时只能显示大约 20 到 40 行,其余几万行对用户来说是看不见的,却白白消耗了内存和渲染时间。

思路:只渲染看得见的那一段

虚拟列表(windowing)的原理是:用一个固定高度的滚动容器,根据 scrollTop 计算出当前可见的起始索引和结束索引,只把这一段数据渲染成真正存在的 DOM 节点,其余位置用上下两个「占位撑高」的空 div 顶住,保证滚动条比例正确。

最小可运行实现

下面是不依赖第三方库的实现,rowHeight 固定,逻辑足够清晰,可直接粘进项目里改。

import { useState, useRef, useCallback } from "react";

interface Row {
  id: number;
  name: string;
}

const ROW_HEIGHT = 36;
const VIEWPORT = 400; // 容器可视高度
const OVERSCAN = 4; // 上下多渲染几行,避免快速滚动白屏

function VirtualList({ rows }: { rows: Row[] }) {
  const [scrollTop, setScrollTop] = useState(0);
  const ref = useRef<HTMLDivElement>(null);

  const total = rows.length;
  const start = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - OVERSCAN);
  const visibleCount = Math.ceil(VIEWPORT / ROW_HEIGHT) + OVERSCAN * 2;
  const end = Math.min(total, start + visibleCount);
  const slice = rows.slice(start, end);

  const onScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
    setScrollTop(e.currentTarget.scrollTop);
  }, []);

  return (
    <div ref={ref} onScroll={onScroll}
      style={{ height: VIEWPORT, overflowY: "auto", position: "relative" }}>
      <div style={{ height: total * ROW_HEIGHT }}>
        <div style={{ transform: `translateY(${start * ROW_HEIGHT}px)` }}>
          {slice.map((r) => (
            <div key={r.id} style={{ height: ROW_HEIGHT, lineHeight: `${ROW_HEIGHT}px` }}>
              {r.name}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// 使用
const data = Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `第 ${i} 行` }));
export default function App() {
  return <VirtualList rows={data} />;
}

关键细节

  • OVERSCAN 必须加:快速滚动时浏览器一帧内可能跳过几十行,多渲染上下各几行能避免白屏闪烁。
  • 用 transform: translateY 偏移而不是改 margin,走合成层,不触发重排。
  • 行高固定时索引计算是常数级;如果行高不固定,需要维护位置缓存或用第三方库(react-window / react-virtualized)估算。
  • 大数据源应当配合分页或无限滚动,虚拟列表解决的是「已有数组很大」的渲染问题,不是「一次性拉全量」的接口问题。

什么时候该上库

固定行高自己写就够了。遇到不定高、横向滚动、表格单元格合并、回收节点复用等复杂场景,直接上 react-window,它的 FixedSizeList 和 VariableSizeList 已经把测量和缓存都做好了,比手写的边界处理更稳。

T

test1

文章作者

问题:一次性渲染十万行会怎样 后台表格、日志面板、聊天记录经常需要渲染成千上万条数据。如果直接用 map 把整个数组渲...

分类
技术
发布时间
2026-09-16
字数
约 2178 字
阅读
1 次