react-window vs react-virtualized vs react-infinite-scroller
Rendering Large Lists in React: Legacy, Power, and Performance
react-windowreact-virtualizedreact-infinite-scroller类似的npm包:

Rendering Large Lists in React: Legacy, Power, and Performance

react-infinite-scroller, react-virtualized, and react-window are all React libraries designed to handle the rendering of large datasets, but they approach the problem with different strategies and levels of abstraction. react-infinite-scroller focuses on the user interaction pattern of infinite scrolling, loading more data as the user reaches the bottom of a list, often rendering all accumulated items in the DOM. react-virtualized is a comprehensive suite of high-performance components that use windowing (virtualization) to render only the visible rows, offering complex features like dynamic heights and grid layouts. react-window is the modern, lightweight successor to react-virtualized, created by the same author to provide a smaller bundle size and a simpler API for standard virtualization needs, though with fewer built-in complex features.

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

Rendering Large Lists in React: Legacy, Power, and Performance

When building data-heavy dashboards, chat interfaces, or feeds, rendering thousands of DOM nodes at once will freeze your browser. The solution is to render only what the user can see. Three libraries have defined this space in the React ecosystem: react-infinite-scroller, react-virtualized, and react-window. While they all solve the "big list" problem, they do so with very different philosophies and trade-offs.

🏗️ Core Architecture: DOM Rendering Strategies

The most critical difference lies in how many DOM nodes each library creates. This decision dictates your application's memory usage and scroll performance.

react-infinite-scroller does not virtualize. It renders every single item you load into the actual DOM. It simply listens for scroll events to tell you when to fetch more data. If you load 5,000 items, you have 5,000 DOM nodes. This works fine for small lists but causes significant lag as the list grows.

// react-infinite-scroller: Renders ALL items in the DOM
import InfiniteScroll from 'react-infinite-scroller';

function Feed() {
  const [items, setItems] = useState(initialItems);

  const loadMore = () => {
    // Fetch next page and append to existing list
    setItems(prev => [...prev, ...newItems]);
  };

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

react-virtualized uses windowing. It calculates which items are visible in the viewport and renders only those, plus a small buffer. It uses absolute positioning to place these items in a container that matches the full height of the dataset. The DOM stays light regardless of data size.

// react-virtualized: Renders ONLY visible items
import { List } from 'react-virtualized';

function Feed({ items }) {
  const rowRenderer = ({ key, index, style }) => (
    <div key={key} style={style} className="item">
      {items[index].content}
    </div>
  );

  return (
    <List
      width={600}
      height={400}
      rowCount={items.length}
      rowHeight={50}
      rowRenderer={rowRenderer}
    />
  );
}

react-window also uses windowing but with a simpler, tree-shakable architecture. Instead of importing a massive List component, you import a lightweight FixedSizeList or VariableSizeList. It functions similarly to react-virtualized under the hood but strips away legacy baggage for a cleaner API.

// react-window: Renders ONLY visible items (Modern API)
import { FixedSizeList } from 'react-window';

function Feed({ items }) {
  const Row = ({ index, style }) => (
    <div style={style} className="item">
      {items[index].content}
    </div>
  );

  return (
    <FixedSizeList
      height={400}
      width={600}
      itemCount={items.length}
      itemSize={50}
    >
      {Row}
    </FixedSizeList>
  );
}

📏 Handling Dynamic Row Heights

One of the hardest problems in virtualization is handling rows that change height based on their content (e.g., a tweet with 1 line of text vs. one with an image and 10 lines).

react-infinite-scroller handles this naturally because it renders everything. The browser's standard layout engine manages the heights automatically. No special configuration is needed.

// react-infinite-scroller: Natural height handling
// No extra code needed; CSS determines height
<div className="item">
  {longContent} {/* Height expands automatically */}
</div>

react-virtualized solves this with CellMeasurer. This component measures the content of a row after it renders and caches the height. The list then uses this cached height for positioning. It is powerful but requires boilerplate code and a cache reference.

// react-virtualized: Complex dynamic heights
import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';

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

const rowRenderer = ({ index, key, parent, style }) => (
  <CellMeasurer
    key={key}
    cache={cache}
    parent={parent}
    columnIndex={0}
    rowIndex={index}
  >
    <div style={style}>
      {items[index].content}
    </div>
  </CellMeasurer>
);

react-window offers VariableSizeList. You must provide an itemSize function that returns the height for a given index. Crucially, you must manually tell the list when a row's height changes so it can recalculate positions. This gives you control but puts the burden of tracking state on you.

// react-window: Controlled dynamic heights
import { VariableSizeList } from 'react-window';

const listRef = useRef(null);

// Tell the list a specific row changed height
listRef.current.resetAfterIndex(index);

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

<VariableSizeList
  ref={listRef}
  height={400}
  width={600}
  itemCount={items.length}
  itemSize={index => getItemHeight(items[index])} // You define this logic
>
  {Row}
</VariableSizeList>

🧩 Feature Set and Extensibility

Beyond basic lists, real-world apps often need grids, infinite loading triggers, or sticky headers.

react-infinite-scroller is specialized for the "infinite" pattern. It provides props like initialLoad, loadMore, and hasMore out of the box. It does not support grids or complex layouts natively; you would need to build those yourself or combine it with other tools.

// react-infinite-scroller: Built-in infinite logic
<InfiniteScroll
  loadMore={loadMore}
  hasMore={hasMore}
  loader={<h4 key={0}>Loading...</h4>}
>
  {/* Items */}
</InfiniteScroll>

react-virtualized is a "kitchen sink" library. It includes Grid, Table, AutoSizer (to fill parent width/height), and InfiniteLoader (to handle data fetching logic). The InfiniteLoader component wraps your list and manages the logic of requesting new pages, which is more robust than the simple callback in react-infinite-scroller.

// react-virtualized: Integrated InfiniteLoader
import { InfiniteLoader, List } from 'react-virtualized';

<InfiniteLoader
  isRowLoaded={({ index }) => !!items[index]}
  loadMoreRows={loadMoreRows}
  rowCount={totalRowCount}
>
  {({ onRowsRendered, registerChild }) => (
    <List
      ref={registerChild}
      onRowsRendered={onRowsRendered}
      // ... other props
    />
  )}
</InfiniteLoader>

react-window is minimalist. It does not include InfiniteLoader or AutoSizer in the core package. Instead, the author publishes them as separate companion packages (react-window-infinite-loader, react-virtualized-auto-sizer). This keeps the core bundle tiny but requires you to install and wire up more dependencies for advanced features.

// react-window: Composable via separate packages
import { FixedSizeList } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import AutoSizer from 'react-virtualized-auto-sizer';

<AutoSizer>
  {({ height, width }) => (
    <InfiniteLoader
      isItemLoaded={isItemLoaded}
      loadMoreItems={loadMoreItems}
      itemCount={itemCount}
    >
      {({ onItemsRendered, ref }) => (
        <FixedSizeList
          height={height}
          width={width}
          itemCount={itemCount}
          itemSize={50}
          onItemsRendered={onItemsRendered}
          ref={ref}
        >
          {Row}
        </FixedSizeList>
      )}
    </InfiniteLoader>
  )}
</AutoSizer>

🛠️ Maintenance and Future Proofing

Choosing a library is also a bet on its future.

react-infinite-scroller sees infrequent updates. While it still works, the fundamental approach of rendering all DOM nodes is increasingly seen as an anti-pattern for large datasets in modern React development. It lacks support for newer React features like concurrent rendering optimizations.

react-virtualized is officially in maintenance mode. The creator, Brian Vaughn, has stated that no new features will be added. It relies on older React patterns (like componentDidMount and legacy context) which can cause warnings or issues in strict mode or future React versions. It remains stable but is effectively frozen.

react-window is the active, recommended path. It was built specifically to address the bundle size and complexity issues of react-virtualized. It uses modern React hooks and patterns, ensuring compatibility with React 18 and beyond. It receives regular updates and bug fixes.

📊 Summary: Key Differences

Featurereact-infinite-scrollerreact-virtualizedreact-window
RenderingFull DOM (No Virtualization)Windowed (Virtualized)Windowed (Virtualized)
PerformancePoor with 1000+ itemsExcellentExcellent
Bundle SizeSmallLarge (~100kb+)Tiny (~15kb)
Dynamic HeightsAutomatic (Native)Complex (CellMeasurer)Manual (resetAfterIndex)
Infinite LoadBuilt-in PropsInfiniteLoader ComponentSeparate Package
StatusLegacyMaintenance ModeActive / Recommended

💡 The Big Picture

react-infinite-scroller is a legacy tool. Avoid it for new development unless you are strictly constrained by an existing codebase that cannot tolerate a refactor. The performance cost of rendering thousands of DOM nodes is too high for modern web standards.

react-virtualized is the heavy-duty tank. It solved hard problems years ago and is incredibly stable. However, its weight and lack of active development make it a poor choice for greenfield projects. Use it only if you need a specific, complex feature (like a very specific grid layout) that react-window cannot easily replicate.

react-window is the modern standard. It provides the performance benefits of virtualization with a fraction of the code. Its modular design encourages best practices, and its active maintenance ensures it will work smoothly with future React updates. For 95% of use cases — from simple dropdowns to massive data grids — react-window is the correct architectural choice.

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

  • react-window:

    Choose react-window for almost all new projects requiring high-performance lists or grids. It offers the best balance of small bundle size, modern API design, and speed. It is the ideal choice when you need to render thousands of items efficiently and your row heights are either fixed or can be estimated reliably without heavy measurement overhead.

  • react-virtualized:

    Choose react-virtualized if your project requires complex, non-standard list behaviors that react-window does not support out of the box, such as intricate dynamic height calculations with CellMeasurer or specific sticky section headers in older React versions. Be aware that this library is in maintenance mode, has a larger bundle size, and uses a more verbose API compared to modern alternatives.

  • react-infinite-scroller:

    Choose react-infinite-scroller only if you are maintaining a legacy codebase that relies on its specific 'load more' trigger logic and you cannot afford a refactor. It is generally not recommended for new projects because it renders all items in the DOM, which leads to severe performance degradation and memory issues as the list grows beyond a few hundred items.

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.