人人都会AI编程

虚拟列表:react-window /react-virtualized

更新时间:2026-07-11

为什么需要虚拟列表

在渲染大量数据时,如果直接使用 map 将所有列表项全部生成 DOM 节点,会导致严重的性能问题。例如,一个包含 10 万条数据的表格,一次性渲染会占用大量内存,引发页面卡顿甚至崩溃。真实场景中,用户一次只能看到视口内有限数量(通常几十条)的记录,其余内容完全不需要提前渲染。

虚拟列表(Virtual List)的思路就是:只渲染用户当前可视区域内的列表项,当用户滚动时动态计算并替换可视区域内的元素。这样无论总数据量多大,实际渲染的 DOM 节点数量始终维持在可控范围内,从而保证流畅的滚动体验。

react-window:轻量高性能的虚拟列表库

react-window 是当前 React 生态中使用最广泛的虚拟列表库之一,由 React 核心团队成员 Brian Vaughn 开发。它极其轻量(bundle 仅约 3KB),API 简洁清晰,支持固定高度和可变高度的列表、网格等多种布局。

基本用法:固定行高列表

import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (
  <div style={style}>
    Row {index}
  </div>
);

function VirtualizedList() {
  return (
    <List
      height={400}       // 列表可视区域高度
      itemCount={100000} // 数据总量
      itemSize={35}      // 每行高度(px)
      width={300}
    >
      {Row}
    </List>
  );
}

react-window 会自动计算可视区域内应该显示哪些项,然后调用 Row 组件渲染每一项,并通过 style 属性控制其位置。开发者无需手动计算滚动偏移量。

动态行高的支持

对于行高不固定的列表,可以使用 VariableSizeList,并提供一个 itemSize 函数或估计值:

import { VariableSizeList as List } from 'react-window';

const getItemSize = index => (index % 2 === 0 ? 50 : 80);

function Row({ index, style }) {
  return <div style={style}>Row {index}</div>;
}

function DynamicList() {
  return (
    <List
      height={400}
      itemCount={1000}
      itemSize={getItemSize}  // 根据 index 返回行高
      width={300}
    >
      {Row}
    </List>
  );
}

如果行高依赖实际渲染内容而无法提前预知,可以结合 useRefmeasureElement 实现自动测量,但需要注意性能开销。

与真实数据结合

通常会从父组件传入数据数组,然后在 Row 中通过 data 属性访问:

const Row = ({ index, style, data }) => (
  <div style={style}>{data[index].name}</div>
);

function App() {
  const items = useMemo(() => fetchLargeData(), []);
  return (
    <FixedSizeList
      height={400}
      itemCount={items.length}
      itemSize={35}
      itemData={items}   // 传递给 Row 的额外数据
      width={300}
    >
      {Row}
    </FixedSizeList>
  );
}

实用场景扩展

  • 滚动到指定项:通过 ref 调用 scrollToItem(index) 方法
  • 无限滚动加载:监听 onScroll 事件,当接近底部时加载更多
  • 网格布局:使用 FixedSizeGridVariableSizeGrid 渲染二维列表

react-virtualized:功能更全面但更重的选择

react-virtualized 是 react-window 的前身,同样由 Brian Vaughn 开发。它提供了更丰富的组件集合,包括 ListGridTableCollection 等,还内置了自动调整大小(AutoSizer)、滚动条定制、无限加载(InfiniteLoader)等功能。

然而,react-virtualized 的 bundle 体积较大(约 30KB gziped),且 API 相对复杂。目前绝大多数新项目都已经转向轻量的 react-window,react-virtualized 主要存在于一些对特定组件(如 TableCellMeasurer)有强依赖的遗留项目中。

典型用法对比

// react-virtualized 需要更多配置
import { List, AutoSizer } from 'react-virtualized';

function rowRenderer({ index, key, style }) {
  return (
    <div key={key} style={style}>
      Row {index}
    </div>
  );
}

function VirtualizedList() {
  return (
    <AutoSizer>
      {({ height, width }) => (
        <List
          height={height}
          rowCount={1000}
          rowHeight={35}
          rowRenderer={rowRenderer}
          width={width}
        />
      )}
    </AutoSizer>
  );
}

选型建议与注意事项

| 特性 | react-window | react-virtualized |
| ------------------ | --------------------- | ---------------------- |
| Bundle 体积 | ~3KB gziped | ~30KB gziped |
| 学习成本 | 低 | 较高 |
| 社区活跃度 | 活跃,主流新项目首选 | 维护但新增功能较少 |
| 内置高级功能 | 基础 | 丰富(AutoSizer、CellMeasurer等) |
| 动态行高支持 | 需自行测量或使用 VariableSizeList | 内置 CellMeasurer 自动测量 |

推荐做法:

  • 新项目直接选择 react-window,它满足了 95% 的虚拟列表需求,且性能极佳。
  • 如果需要自动行高测量、复杂表格、自动尺寸检测等高级特性,可考虑 react-virtualized,或引入 react-window + 自己实现 AutoSizer(仅需少量代码)。
  • 避免在虚拟列表项内使用过于复杂的嵌套组件,因为频繁的重建和回收会放大性能问题。
  • 结合 React.memo 优化 Row 组件,避免不必要的重新渲染:
const Row = React.memo(({ index, style, data }) => {
  return <div style={style}>{data[index]}</div>;
});
  • 注意,虚拟列表本质上是牺牲滚动条的自然行为(DOM 节点数量恒定)来换取性能,因此scroll 事件的 scrollToptarget 等行为与普通列表有所不同,开发时需留意。

虚拟列表是处理大数据量渲染的利器,正确使用可以让页面滚动如丝般顺滑,是 React 性能优化中不可忽视的一环。