react虚拟列表实现及原理

好问题 👍

React 虚拟列表(Virtual List,也叫虚拟滚动 Virtual Scrolling)是 前端性能优化的核心技术之一,尤其在需要渲染上万条数据的表格或列表时很关键。下面我帮你拆解:


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

  • 普通做法:一次性渲染所有 DOM(比如 1 万条 <div>),会让浏览器卡顿,首屏加载慢。
  • 虚拟列表:只渲染 可视区域的元素 ,其他部分用空白占位,随着滚动动态更新。
    👉 这样 DOM 数量始终保持在几十到几百之间,性能大幅提升。

🔹 二、基本原理

虚拟列表的核心思想是 "只渲染视口范围内的元素 + 占位保持滚动条高度"

  1. 计算容器高度

    • 根据数据总数 × 单项高度 = 总高度(生成一个大容器,撑起滚动条)。
  2. 计算可见区域

    • 监听滚动事件,得到 scrollTop
    • 根据 scrollTop / itemHeight 算出应该显示哪些元素。
  3. 渲染可见元素

    • absolute 定位,把可见元素放在容器的正确位置。
    • 其他元素不渲染,靠容器的 paddingtranslateY 占位。

🔹 三、React 简单实现示例

假设每行高度固定(50px),一共 10000 条数据。

ini 复制代码
import React, { useState, useRef } from "react";

const rowHeight = 50; // 每项高度
const total = 10000;  // 数据总数
const visibleCount = 10; // 可视区显示多少项(容器高度 / rowHeight)

const data = Array.from({ length: total }, (_, i) => `Item ${i + 1}`);

export default function VirtualList() {
  const [start, setStart] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);

  const handleScroll = () => {
    if (!containerRef.current) return;
    const scrollTop = containerRef.current.scrollTop;
    const newStart = Math.floor(scrollTop / rowHeight);
    setStart(newStart);
  };

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

  return (
    <div
      ref={containerRef}
      onScroll={handleScroll}
      style={{
        height: rowHeight * visibleCount,
        overflowY: "auto",
        border: "1px solid #ccc",
        position: "relative",
      }}
    >
      {/* 大容器:撑起滚动条 */}
      <div style={{ height: total * rowHeight, position: "relative" }}>
        {/* 可视区元素 */}
        {visibleData.map((item, i) => (
          <div
            key={item}
            style={{
              position: "absolute",
              top: (start + i) * rowHeight,
              height: rowHeight,
              lineHeight: `${rowHeight}px`,
              borderBottom: "1px solid #eee",
              boxSizing: "border-box",
              width: "100%",
            }}
          >
            {item}
          </div>
        ))}
      </div>
    </div>
  );
}

👉 效果:

  • 容器始终只有 ~10 个 DOM 元素。
  • 滚动时动态替换显示的元素。
  • 看起来就像 10000 条都在 DOM 里,但实际上只有少量渲染。

🔹 四、进阶优化

  1. 动态高度支持

    • 如果每项高度不同,需要预先计算累积高度(类似 "前缀和"),滚动时二分查找定位。
    • 比如 react-virtualizedCellMeasurer
  2. 缓冲区(overscan)

    • 在可视区域上下多渲染几条,避免快速滚动时白屏。
  3. 分片渲染(时间切片)

    • 避免一次性渲染太多(requestIdleCallback 或 React 18 startTransition)。

🔹 五、成熟方案

如果自己写太复杂,可以直接用社区库:

  • react-window(轻量,推荐,适合固定高度)
  • react-virtualized(功能强大,支持动态高度、表格、Grid)
  • rc-virtual-list(Ant Design 内部用的虚拟列表)
javascript 复制代码
import { FixedSizeList as List } from "react-window";

<List
  height={500}
  itemCount={10000}
  itemSize={50}
  width="100%"
>
  {({ index, style }) => (
    <div style={style}>Row {index}</div>
  )}
</List>

🔹 六、总结

  • 原理:可见区域渲染 + 容器占位 + 滚动时替换数据。
  • 优势:DOM 数量固定,内存和渲染性能提升明显。
  • 扩展:支持动态高度、缓冲区、时间切片。
  • 推荐库:react-window(简单)、react-virtualized(复杂场景)。

相关推荐
杰克尼3 小时前
vue_day06
前端·javascript·vue.js
DcTbnk3 小时前
ESM (放到打包里 import) 和 IIFE (URL 动态 loadScript)
前端·javascript
狗哥哥3 小时前
从文档到代码:工程化规则体系落地全指南
前端
San303 小时前
深入理解 JavaScript 中的 Symbol:独一无二的值
前端·javascript·ecmascript 6
就叫飞六吧3 小时前
css+js 前端无限画布实现
前端·javascript·css
2501_941148153 小时前
高并发搜索引擎Elasticsearch与Solr深度优化在互联网实践分享
java·开发语言·前端
IT 前端 张3 小时前
Uniapp全局显示 悬浮组件/无需单页面引入
前端·javascript·uni-app
allenjiao3 小时前
WebGPU vs WebGL:WebGPU什么时候能完全替代WebGL?Web 图形渲染的迭代与未来
前端·图形渲染·webgl·threejs·cesium·webgpu·babylonjs
上车函予4 小时前
geojson-3d-renderer:从原理到实践,打造高性能3D地理可视化库
前端·vue.js·three.js
孟祥_成都4 小时前
别被营销号误导了!你以为真的 Bun 和 Deno 比 Node.js 快很多吗?
前端·node.js