react-window vs react-virtualized vs react-infinite-scroller
React 长列表渲染方案:虚拟滚动与无限加载的深度对比
react-windowreact-virtualizedreact-infinite-scroller类似的npm包:

React 长列表渲染方案:虚拟滚动与无限加载的深度对比

react-infinite-scroller、react-virtualized 和 react-window 都是 React 生态中处理长列表性能问题的核心库,但它们的解决思路截然不同。react-infinite-scroller 专注于“无限加载”模式,通过监听滚动事件动态追加 DOM 节点,适合日志流或社交feed流场景。react-virtualized 是一个功能全面的旧式虚拟滚动库,提供了网格、表格、自动Sizer等复杂组件,但架构较重且已停止维护。react-window 是 react-virtualized 作者推出的现代化轻量级替代品,专注于核心的虚拟滚动逻辑,API 更简洁,性能更优,是目前处理大规模数据列表的首选方案。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
react-window6,152,43117,200417 kB08 分钟前MIT
react-virtualized1,723,75827,0671.31 MB18 天前MIT
react-infinite-scroller03,29230.3 kB98-MIT

React 长列表渲染方案:react-infinite-scroller vs react-virtualized vs react-window

在构建 React 应用时,处理长列表是一个经典的性能挑战。当列表项达到数百甚至数千条时,直接渲染所有 DOM 节点会导致页面卡顿、内存飙升。react-infinite-scroller、react-virtualized 和 react-window 提供了三种不同的解决路径。本文将深入剖析它们的技术原理、适用场景及代码实现,帮助你做出正确的架构决策。

🔄 核心机制:追加渲染 vs 虚拟裁剪

这三个库最根本的区别在于它们如何处理 DOM 节点。

react-infinite-scroller 采用的是增量追加策略。它不会移除旧的节点,而是随着用户滚动,不断将新数据对应的 DOM 元素追加到容器底部。这意味着列表越长,DOM 节点越多。

// react-infinite-scroller: 随着滚动,DOM 节点持续增加
import InfiniteScroll from 'react-infinite-scroller';

function NewsFeed() {
  const [items, setItems] = useState(initialData);

  const fetchMoreData = () => {
    // 模拟加载下一页数据并追加到现有列表
    const newItems = generateMoreItems();
    setItems(prev => [...prev, ...newItems]);
  };

  return (
    <InfiniteScroll
      pageStart={0}
      loadMore={fetchMoreData}
      hasMore={true}
      initialLoad={false}
    >
      {items.map(item => (
        <div key={item.id} className="item">{item.content}</div>
      ))}
    </InfiniteScroll>
  );
}

react-virtualized 和 react-window 则采用**虚拟裁剪(Virtualization)**策略。它们只渲染当前可视窗口(Viewport)内的元素以及少量的缓冲区元素。当用户滚动时,它们通过绝对定位(transform 或 top/left)快速复用或重新定位现有的 DOM 节点,而不是创建新节点。无论列表有一万条还是一百万条,DOM 节点数量始终保持在几十个左右。

// react-window: 仅渲染可视区域内的节点,总数恒定
import { FixedSizeList } from 'react-window';

function LargeDataset() {
  const Row = ({ index, style }) => (
    <div style={style} className="row">
      Row {index}
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={10000}
      itemSize={35}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

// react-virtualized: 同样采用虚拟裁剪,但 API 较为繁琐
import { List } from 'react-virtualized';

function LegacyVirtualList() {
  const rowRenderer = ({ key, index, style }) => (
    <div key={key} style={style} className="row">
      Row {index}
    </div>
  );

  return (
    <List
      width={800}
      height={600}
      rowCount={10000}
      rowHeight={35}
      rowRenderer={rowRenderer}
    />
  );
}

📏 高度处理:固定 vs 动态

在处理列表项高度不一致的场景时,三个库的表现和实现复杂度差异巨大。

react-infinite-scroller 对高度没有特殊要求,因为它只是简单地追加 DOM。浏览器会自然处理不同高度的元素布局,开发者无需额外配置。

// react-infinite-scroller: 天然支持动态高度,无需特殊配置
{items.map(item => (
  <div key={item.id} style={{ height: item.expanded ? 200 : 50 }}>
    {item.content}
  </div>
))}

react-window 提供了专门的 VariableSizeList 组件来处理动态高度。你需要维护一个高度缓存映射,并在数据变化时通知库重新计算布局。API 设计清晰,逻辑可控。

// react-window: 显式处理动态高度
import { VariableSizeList } from 'react-window';

const listRef = useRef();
const itemHeights = useRef(new Map());

const Row = ({ index, style }) => (
  <div style={style}>
    {/* 内容变化时调用 listRef.current.resetAfterIndex(index) */}
    Dynamic Content {index}
  </div>
);

const getItemSize = (index) => itemHeights.current.get(index) || 50;

<VariableSizeList
  height={600}
  itemCount={items.length}
  itemSize={getItemSize}
  width="100%"
  ref={listRef}
>
  {Row}
</VariableSizeList>

react-virtualized 使用 CellMeasurer 和 CellMeasurerCache 来处理动态高度。这种方法虽然功能强大,但样板代码非常多,容易出错,且性能开销略大于 react-window 的现代实现。

// react-virtualized: 动态高度需要复杂的 CellMeasurer 包装
import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';

const cache = new CellMeasurerCache({
  fixedWidth: true,
  defaultHeight: 100
});

const rowRenderer = ({ key, index, parent, style }) => (
  <CellMeasurer
    key={key}
    cache={cache}
    parent={parent}
    columnIndex={0}
    rowIndex={index}
  >
    {({ measure }) => (
      <div style={style} onLoad={measure}>
        Complex Content {index}
      </div>
    )}
  </CellMeasurer>
);

<List
  width={800}
  height={600}
  rowCount={1000}
  rowHeight={cache.rowHeight}
  deferredMeasurementCache={cache}
  rowRenderer={rowRenderer}
/>

🛠️ 功能范围与架构哲学

react-virtualized 是一个“大而全”的库。它不仅提供列表,还提供了 Grid(网格)、Table(表格)、AutoSizer(自适应容器)、Collection 等组件。这种设计在 2016 年非常先进,但也导致了库的体积庞大,且内部依赖复杂。由于其作者 Brian Vaughn 已明确表示不再积极维护该库,转而开发 react-window,因此它存在技术债务累积的风险。

react-window 奉行“小而美”的哲学。它剥离了表格、网格等高级组件,专注于提供最核心的虚拟列表和网格原语(FixedSizeList, VariableSizeGrid 等)。如果需要表格功能,官方推荐组合使用 react-window 和简单的 CSS Grid 或 Flexbox,或者使用社区基于它构建的上层库(如 react-window-infinite-loader)。这种设计使得它极其轻量,易于树摇(Tree-shaking),且更容易理解源码。

react-infinite-scroller 则是一个单一功能库。它不做虚拟滚动,只做无限加载的事件监听和占位符管理。它的价值在于简单,适合那些不需要虚拟滚动优化的小型列表场景。

⚠️ 维护状态与未来风险

这是一个关键的架构决策点。

  • react-virtualized: 已停止维护(Unmaintained)。虽然它依然稳定可用,但不会再有新特性加入,严重的 Bug 修复也可能变慢。官方文档明确建议新项目使用 react-window。
  • react-window: 积极维护中。它是目前的行业标准,拥有活跃的社区支持和持续的更新。
  • react-infinite-scroller: 维护状态一般。功能稳定,但由于其原理限制(DOM 节点无限增长),在现代高性能 Web 应用中的适用范围越来越窄。

📊 场景决策矩阵

场景特征推荐方案理由
数据量巨大 (>1000 条)react-window必须使用虚拟裁剪以保持帧率稳定,DOM 节点数恒定。
需要无限加载 APIreact-window + react-window-infinite-loader结合虚拟滚动与无限加载,既保证性能又支持分页。
数据量较小 (<500 条) 且需无限加载react-infinite-scroller实现最简单,无需计算高度,开发速度快。
遗留项目维护react-virtualized如果老项目已在用,迁移成本高可暂时保留,但新功能建议用 react-window。
复杂表格/网格react-window (配合自定义渲染)避免 react-virtualized 的沉重包袱,按需构建轻量表格。

💡 专家建议

在现代前端架构中,react-window 应该是你的默认选择。它代表了虚拟滚动技术的最佳实践:轻量、快速、API 清晰。

只有当你确定列表数据量永远很小,且业务逻辑强依赖于“滚动到底部自动加载下一页”这种交互模式,而不关心 DOM 节点累积带来的潜在性能损耗时,才考虑 react-infinite-scroller。

至于 react-virtualized,请将其视为“遗留技术”。除非你被绑定在旧代码库中,否则不要在新项目中引入它。它的复杂性已经不再符合当今 React 生态对简洁和性能的极致追求。

记住: 虚拟滚动的核心不是“加载更多”,而是“少渲染”。选择正确的工具,能让你的应用在数据量激增时依然丝滑流畅。

如何选择: react-window vs react-virtualized vs react-infinite-scroller

  • react-window:

    当你需要渲染成千上万条固定高度或动态高度的列表项,且追求极致的渲染性能和最小的打包体积时,react-window 是最佳选择。它专注于“虚拟窗口”技术,只渲染可视区域内的元素,API 设计简单直观(如 FixedSizeList 和 VariableSizeList),非常适合后台管理系统、大型数据表格或聊天历史记录等场景。

  • react-virtualized:

    除非你需要维护遗留项目,否则不建议在新项目中使用 react-virtualized。该库已不再积极维护,且其架构过于庞大,引入了许多现代应用不再需要的复杂组件(如 Collection、Grid 的旧实现)。它的 bundle 体积较大,且 API 设计较为繁琐,学习曲线陡峭。

  • react-infinite-scroller:

    如果你的业务场景严格依赖“无限滚动加载”(即用户滚到底部时触发 API 请求加载下一页),且数据总量不可预知,选择 react-infinite-scroller。它不执行虚拟裁剪,而是真实渲染所有节点,因此仅适用于列表项总数在几百到一千以内的场景,否则会导致 DOM 节点过多引发卡顿。

react-window的README

react-window logo

react-window is a component library that helps render large lists of data quickly and without the performance problems that often go along with rendering a lot of data. It's used in a lot of places, from React DevTools to the Replay browser.

Support

If you like this project there are several ways to support it:

The following wonderful companies and individuals have sponsored react-window:

Installation

Begin by installing the library from NPM:

npm install react-window

TypeScript types

TypeScript definitions are included within the published dist folder

FAQs

Frequently asked questions can be found here.

Documentation

Documentation for this project is available at react-window.vercel.app.

List

Renders data with many rows.

Required props

NameDescription
rowComponent

React component responsible for rendering a row.

This component will receive an index and style prop by default. Additionally it will receive prop values passed to rowProps.

ℹ️ The prop types for this component are exported as RowComponentProps

rowCount

Number of items to be rendered in the list.

rowHeight

Row height; the following formats are supported:

  • number of pixels (number)
  • percentage of the list's current height (string)
  • function that returns the row height (in pixels) given an index and rowProps
  • dynamic row height cache returned by the useDynamicRowHeight hook

ℹ️ Percentage heights can't be resolved during server rendering unless a defaultHeight (or a pixel height via the style prop) is specified.

⚠️ Dynamic row heights are not as efficient as predetermined sizes. It's recommended to provide your own height values if they can be determined ahead of time.

rowProps

Additional props to be passed to the row-rendering component. List will automatically re-render rows when values in this object change.

⚠️ This object must not contain ariaAttributes, index, or style props.

Optional props

NameDescription
className

CSS class name.

style

Optional CSS properties. The list of rows will fill the height defined by this style.

children

Additional content to be rendered within the list (above rows). This property can be used to render things like overlays or tooltips.

defaultHeight

Default height of list (in pixels) for initial render, before its actual height has been measured.

ℹ️ Lists can't be measured during server rendering, so this value determines how many rows are server rendered. It's recommended to set this to the expected height of the list (or to specify a pixel height via the style prop).

defaultScrollOffset

Initial scroll offset (in pixels) of the list. This value is only applied once, when the list first mounts with at least one row.

ℹ️ Server-rendered HTML can't be pre-scrolled, so server rendering always starts at the first row; the default scroll offset is applied once the list hydrates.

⚠️ If the list can't be scrolled that far when the offset is applied (e.g. it is hidden with display: none, or uses percentage row heights before its height has been measured), the offset is clamped or ignored and not retried. Specify a defaultHeight (or a pixel height via the style prop) when using percentage row heights.

listRef

Ref used to interact with this component's imperative API.

This API has imperative methods for scrolling and a getter for the outermost DOM element.

ℹ️ The useListRef and useListCallbackRef hooks are exported for convenience in TypeScript projects.

onResize

Callback notified when the List's outermost HTMLElement resizes. This may be used to (re)scroll a row into view.

onRowsRendered

Callback notified when the range of visible rows changes.

overscanCount

How many additional rows to render outside of the visible area. This can reduce visual flickering near the edges of a list when scrolling.

rowKey

Lists use the row index as a key by default. This prop can provide a custom key value.

ℹ️ Custom keys can ensure better UX for sortable or filterable lists, particularly if your row components are stateful. Refer to the React documentation for more info.

⚠️ This prop cannot be auto-memoized because it is called during render. It is important to always useCallback for this prop; do not use an inline function.

scrollElement

Element that scrolls the list, for lists that scroll along with other content (e.g. the page):

  • "window" to scroll with the page
  • an ancestor element that scrolls (e.g. stored with a useState ref callback); null until it has mounted

The list renders at its full height and only renders the rows visible within the scroll element's viewport. Its position within the scroll element is measured whenever it renders, scrolls, or resizes, and whenever content before it is resized, inserted, or removed (e.g. an image loading above the list).

  • scrollToRow scrolls the scroll element
  • defaultHeight is used as the viewport height until the scroll element has been measured (e.g. during server rendering)
  • percentage row heights are relative to the scroll element's viewport height
  • defaultScrollOffset is ignored

ℹ️ While the scroll element is null, the list renders the first rows (as it does during server rendering).

⚠️ The list itself doesn't scroll, so its onScroll handler isn't called; listen to the scroll element's "scroll" events instead (or use the useIsScrolling hook).

tagName

Can be used to override the root HTML element rendered by the List component. The default value is "div", meaning that List renders an HTMLDivElement as its root.

⚠️ In most use cases the default ARIA roles are sufficient and this prop is not needed.

Grid

Renders data with many rows and columns.

ℹ️ Unlike List rows, Grid cell sizes must be known ahead of time. Either static sizes or something that can be derived (from the data in CellProps) without rendering.

Required props

NameDescription
cellComponent

React component responsible for rendering a cell.

This component will receive columnIndex, rowIndex, and style props by default. Additionally it will receive prop values passed to cellProps.

ℹ️ The prop types for this component are exported as CellComponentProps

cellProps

Additional props to be passed to the cell-rendering component. Grid will automatically re-render cells when values in this object change.

⚠️ This object must not contain ariaAttributes, columnIndex, rowIndex, or style props.

columnCount

Number of columns to be rendered in the grid.

columnWidth

Column width; the following formats are supported:

  • number of pixels (number)
  • percentage of the grid's current width (string)
  • function that returns the column width (in pixels) given an index and cellProps

ℹ️ Percentage widths can't be resolved during server rendering unless a defaultWidth (or a pixel width via the style prop) is specified.

rowCount

Number of rows to be rendered in the grid.

rowHeight

Row height; the following formats are supported:

  • number of pixels (number)
  • percentage of the grid's current height (string)
  • function that returns the row height (in pixels) given an index and cellProps

ℹ️ Percentage heights can't be resolved during server rendering unless a defaultHeight (or a pixel height via the style prop) is specified.

Optional props

NameDescription
className

CSS class name.

dir

Indicates the directionality of grid cells.

ℹ️ The directionality of an ancestor element can't be detected during server rendering; specify this prop explicitly for server-rendered right-to-left grids.

ℹ️ See HTML dir global attribute for more information.

style

Optional CSS properties. The grid of cells will fill the height and width defined by this style.

children

Additional content to be rendered within the grid (above cells). This property can be used to render things like overlays or tooltips.

columnKey

Grids use the column index as a key by default. This prop can be used along with the rowKey prop to provide a custom key value.

ℹ️ Custom keys can ensure better UX for sortable or filterable grids, particularly if your cell components are stateful. Refer to the React documentation for more info.

⚠️ This prop cannot be auto-memoized because it is called during render. It is important to always useCallback for this prop; do not use an inline function.

defaultHeight

Default height of grid (in pixels) for initial render, before its actual height has been measured.

ℹ️ Grids can't be measured during server rendering, so this value determines how many rows are server rendered. It's recommended to set this to the expected height of the grid (or to specify a pixel height via the style prop).

defaultScrollLeft

Initial horizontal scroll offset (in pixels) of the grid. This value is only applied once, when the grid first mounts with at least one column.

ℹ️ Server-rendered HTML can't be pre-scrolled, so server rendering always starts at the first column; the default scroll offset is applied once the grid hydrates.

⚠️ If the grid can't be scrolled that far when the offset is applied (e.g. it is hidden with display: none, or uses percentage column widths before its width has been measured), the offset is clamped or ignored and not retried. Specify a defaultWidth (or a pixel width via the style prop) when using percentage column widths.

defaultScrollTop

Initial vertical scroll offset (in pixels) of the grid. This value is only applied once, when the grid first mounts with at least one row.

ℹ️ Server-rendered HTML can't be pre-scrolled, so server rendering always starts at the first row; the default scroll offset is applied once the grid hydrates.

⚠️ If the grid can't be scrolled that far when the offset is applied (e.g. it is hidden with display: none, or uses percentage row heights before its height has been measured), the offset is clamped or ignored and not retried. Specify a defaultHeight (or a pixel height via the style prop) when using percentage row heights.

defaultWidth

Default width of grid (in pixels) for initial render, before its actual width has been measured.

ℹ️ Grids can't be measured during server rendering, so this value determines how many columns are server rendered. It's recommended to set this to the expected width of the grid (or to specify a pixel width via the style prop).

gridRef

Imperative Grid API.

ℹ️ The useGridRef and useGridCallbackRef hooks are exported for convenience in TypeScript projects.

onCellsRendered

Callback notified when the range of rendered cells changes.

onResize

Callback notified when the Grid's outermost HTMLElement resizes. This may be used to (re)scroll a cell into view.

overscanCount

How many additional rows/columns to render outside of the visible area. This can reduce visual flickering near the edges of a grid when scrolling.

rowKey

Grids use the row index as a key by default. This prop can be used along with the columnKey prop to provide a custom key value.

ℹ️ Custom keys can ensure better UX for sortable or filterable grids, particularly if your cell components are stateful. Refer to the React documentation for more info.

⚠️ This prop cannot be auto-memoized because it is called during render. It is important to always useCallback for this prop; do not use an inline function.

scrollElement

Element that scrolls the grid, for grids that scroll along with other content (e.g. the page):

  • "window" to scroll with the page
  • an ancestor element that scrolls (e.g. stored with a useState ref callback); null until it has mounted

The grid renders at its full height and only renders the rows (and columns) visible within the scroll element's viewport. Its position within the scroll element is measured whenever it renders, scrolls, or resizes, and whenever content before it is resized, inserted, or removed (e.g. an image loading above the grid).

  • columns scroll with the scroll element too if the grid is as wide as its content (e.g. style={{ width: "max-content" }}); otherwise the grid fills the width of its parent and its columns scroll within it
  • scrollToCell, scrollToColumn and scrollToRow scroll whichever element scrolls each axis
  • defaultHeight and defaultWidth are used as the viewport size until the scroll element has been measured (e.g. during server rendering)
  • percentage row heights and column widths are relative to the scroll element's viewport
  • defaultScrollLeft and defaultScrollTop are ignored

ℹ️ While the scroll element is null, the grid renders the first rows (as it does during server rendering).

⚠️ The grid's onScroll handler is only called when the grid scrolls its own columns; listen to the scroll element's "scroll" events instead (or use the useIsScrolling hook).

tagName

Can be used to override the root HTML element rendered by the Grid component. The default value is "div", meaning that Grid renders an HTMLDivElement as its root.

⚠️ In most use cases the default ARIA roles are sufficient and this prop is not needed.