react-list vs react-virtual vs react-infinite vs react-virtualized vs react-window vs react-window-infinite-loader
React 無限スクロールと仮想化ライブラリの技術選定ガイド
react-listreact-virtualreact-infinitereact-virtualizedreact-windowreact-window-infinite-loader類似パッケージ:

React 無限スクロールと仮想化ライブラリの技術選定ガイド

このドキュメントは、React アプリケーションで大規模なリストや無限スクロールを実装する際の主要なライブラリを比較しています。react-virtualizedreact-windowreact-virtual などの仮想化ライブラリから、react-infinitereact-list などの従来の無限スクロールコンポーネントまで、それぞれのアーキテクチャ、API デザイン、メンテナンス状況を分析します。パフォーマンス、柔軟性、開発者体験の観点から、プロジェクトに適した技術選定を行うための指針を提供します。

npmのダウンロードトレンド

3 年

GitHub Starsランキング

統計詳細

パッケージ
ダウンロード数
Stars
サイズ
Issues
公開日時
ライセンス
react-list500,7561,97434.9 kB711年前MIT
react-virtual467,6336,853158 kB102-MIT
react-infinite11,3492,686243 kB102-BSD-3-Clause
react-virtualized027,0692.24 MB01年前MIT
react-window017,160209 kB22ヶ月前MIT
react-window-infinite-loader095123 kB03ヶ月前MIT

React 無限スクロールと仮想化ライブラリ:アーキテクチャと実装の深層比較

大規模なリストを扱う際、すべてのアイテムを一度にレンダリングすると、ブラウザが重くなりユーザー体験が損なわれます。これを解決するため、表示されている部分だけを描画する「ウィンドウイング(仮想化)」技術や、スクロールに応じてデータを読み込む「無限スクロール」実装が必要になります。

今回は、react-infinitereact-listreact-virtualreact-virtualizedreact-windowreact-window-infinite-loader の 6 つのパッケージを比較し、それぞれの技術的特徴と適切な使いどころを明確にします。

🏗️ アーキテクチャ:コンポーネント型 vs ヘッドレス型

ライブラリを選ぶ際、まず「完成されたコンポーネントを提供するか」それとも「ロジックだけを提供するか(ヘッドレス)」という違いを理解する必要があります。

react-virtualizedreact-window は、高度に最適化されたコンポーネントを提供します。

  • 内部で DOM 計算を最適化しており、設定項目を渡すだけで高速なリストが得られます。
  • 黒箱化された部分が多く、カスタマイズには一定の学習コストがかかります。
// react-window: 完成されたコンポーネントを使用
import { FixedSizeList } from 'react-window';

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

const List = () => (
  <FixedSizeList height={600} itemCount={1000} itemSize={35}>
    {Row}
  </FixedSizeList>
);

react-virtual はヘッドレスアプローチを採用しています。

  • UI に関する制限がなく、開発者が HTML 構造やスタイルを完全に制御できます。
  • 計算ロジック(どのアイテムを表示すべきか)だけをフックとして提供します。
// react-virtual: 仮想化ロジックだけをフックで取得
import { useVirtual } from 'react-virtual';
import { useRef } from 'react';

const List = () => {
  const parentRef = useRef();
  const virtual = useVirtual({
    size: 1000,
    parentRef,
    estimateSize: () => 35,
  });

  return (
    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
      <div style={{ height: `${virtual.totalSize}px` }}>
        {virtual.virtualItems.map(item => (
          <div key={item.key} style={{ position: 'absolute', top: 0, transform: `translateY(${item.start}px)` }}>
            Row {item.index}
          </div>
        ))}
      </div>
    </div>
  );
};

react-infinitereact-list は、より単純なコンポーネント型ですが、内部最適化は古いです。

  • 仮想化というより、単純な無限スクロールイベントの検知に近いです。
  • 大量のデータがある場合、パフォーマンス劣化のリスクがあります。
// react-infinite: 単純なコンポーネント構成
import Infinite from 'react-infinite';

const List = () => (
  <Infinite elementHeight={50} containerHeight={600}>
    <div>Item 1</div>
    <div>Item 2</div>
    {/* 子要素をすべて渡す必要がある場合が多い */}
  </Infinite>
);

📦 保守性とメンテナンス状況

ライブラリの長期的なメンテナンスは、プロジェクトの安定性に直結します。

react-virtualized は事実上メンテナンスモードに入っています。

  • 作者である Brian Vaughn 氏自身によって、より軽量な react-window への移行が推奨されています。
  • 機能は豊富ですが、バンドルサイズが大きく、ツリーシェイキングが効きにくい構造です。

react-window が現在のデファクトスタンダードです。

  • react-virtualized の欠点を解消するために作られました。
  • API がモダンで、バンドルサイズも小さく、React の最新機能にも対応しています。

react-infinitereact-list は更新頻度が低いです。

  • 長期間更新がない場合が多く、現代の React バージョン(特に Strict Mode や Concurrent Features)との互換性に懸念があります。
  • 新規プロジェクトで採用すると、将来的な技術的負債になる可能性が高いです。

react-virtual は活発にメンテナンスされています。

  • TanStack (旧 React Table) のエコシステムの一部として開発されています。
  • React 以外のフレームワーク対応も視野に入れているため、設計が堅牢です。

🔄 無限ローディングの実装パターン

リストが無限に続く場合(ページネーションなしでスクロールするとデータが追加される)、追加のロジックが必要になります。

react-window-infinite-loader は、react-window のために作られた専用ラッパーです。

  • 「どのアイテムが既に読み込まれているか」を管理するロジックを提供します。
  • react-window と組み合わせることで、高性能な無限スクロールが実現できます。
// react-window-infinite-loader: 無限ローディングの管理
import InfiniteLoader from 'react-window-infinite-loader';
import { FixedSizeList } from 'react-window';

const isItemLoaded = (index) => !!items[index];
const loadMoreItems = (startIndex, stopIndex) => {
  // API を叩いてデータを取得し、items 配列を更新する
};

const List = () => (
  <InfiniteLoader
    isItemLoaded={isItemLoaded}
    itemCount={items.length}
    loadMoreItems={loadMoreItems}
  >
    {({ onItemsRendered, ref }) => (
      <FixedSizeList
        itemCount={items.length}
        itemSize={35}
        onItemsRendered={onItemsRendered}
        ref={ref}
      >
        {Row}
      </FixedSizeList>
    )}
  </InfiniteLoader>
);

react-virtual にも同様の機能がありますが、より低レベルです。

  • 開発者がスクロールイベントを監視し、自前でデータ取得ロジックを書く必要があります。
  • 自由度が高い分、実装コストは上がります。
// react-virtual: 自前でローディングロジックを制御
const virtual = useVirtual({
  size: items.length,
  parentRef,
  estimateSize: () => 35,
  overscan: 5,
});

// onScroll などで末端に達したことを検知し、loadMoreItems を発火させる必要がある

react-infinite は無限スクロールが主目的です。

  • 初期設計から無限スクロールを前提としているため、設定は簡単です。
  • しかし、内部で多くの DOM 要素を保持しようとする傾向があり、メモリ使用量が増大するリスクがあります。
// react-infinite: 無限スクロールイベントをトリガーに使用
<Infinite
  useWindowAsScrollContainer
  onInfiniteLoad={loadMoreItems}
  loadingSpinnerContainer={<div>Loading...</div>}
>
  {items.map(item => <div key={item.id}>{item.name}</div>)}
</Infinite>

⚡ パフォーマンスと最適化の仕組み

大量のデータを扱う場合、いかに DOM 操作を減らすかが鍵になります。

特徴react-window / react-virtualizedreact-virtualreact-infinite / react-list
レンダリング表示領域のみレンダリング(ウィンドウイング)表示領域のみレンダリング全要素を保持しようとする傾向
DOM 更新最小限に抑えられた更新開発者の実装に依存頻繁な更新が発生しやすい
スクロール専用コンテナでのスクロール制御任意のコンテナで制御可能ウィンドウスクロールに依存しがち
メモリ効率的効率的高負荷になりやすい

react-window は、アイテムのサイズが固定か変動かに応じてコンポーネントを使い分けます。

  • FixedSizeList: 高速。すべてのアイテムが同じ高さの場合。
  • VariableSizeList: 柔軟。アイテムの高さが異なる場合(計算コストが少し増える)。
// 固定サイズの場合(最速)
<FixedSizeList itemSize={50} ... />

// 可変サイズの場合(柔軟)
<VariableSizeList itemSize={index => getHeight(index)} ... />

react-virtualized も同様の機能を持ちますが、API が複雑です。

  • ListGridTable など用途別にコンポーネントが細分化されています。
  • 機能は強力ですが、不要な機能までバンドルに含まれやすい構造です。

🛠️ 開発者体験(DX)とカスタマイズ性

実装のしやすさや、デザインのカスタマイズ自由度も重要な選定基準です。

react-window はバランスが良いです。

  • 標準的なリストなら数行で実装できます。
  • カスタマイズには「レンダラープロップス」を介して行います。

react-virtual はカスタマイズ性が最高です。

  • 返ってくるのは数値(位置情報)と参照(ref)だけなので、HTML 構造は自由です。
  • 複雑なグリッドレイアウトや、特殊なアニメーションを組み込みたい場合に真価を発揮します。
// react-virtual: 自由な HTML 構造が可能
<div ref={parentRef}>
  {virtualItems.map(item => (
    <div key={item.key} style={{ ...item.measurements }}>
      {/* 中に何をいれても自由 */}
    </div>
  ))}
</div>

react-infinite は簡単ですが制限があります。

  • コンテナ構造が固定されており、CSS で自由に制御するのが難しい場合があります。
  • 現代の CSS 手法(Flexbox, Grid)との相性が悪いことがあります。

📊 選定マトリクス:どのライブラリを使うべきか

プロジェクトの要件に応じて、以下のように選定するのが合理的です。

要件推奨ライブラリ理由
標準的なリストreact-window軽量、高速、メンテナンスが活発
無限スクロールreact-window + react-window-infinite-loader組み合わせることで最強の性能と機能
完全なカスタマイズreact-virtualUI 制約がなく、ロジックのみ借用できる
レガシー維持react-virtualized既存実装がある場合のみ。新規は非推奨
シンプルな実装react-infinite非推奨。データ量少ならありだがリスク大
他フレームワークreact-virtual核心ロジックがフレームワーク非依存

💡 結論:現代のベストプラクティス

2024 年以降の React プロジェクトにおいて、リストの仮想化が必要な場合、react-window が最も堅実な選択です。特に react-window-infinite-loader と組み合わせることで、無限スクロールの課題も解決できます。API が直感的で、コミュニティのサポートも厚いため、トラブルが起きた際の解決も容易です。

一方、デザインシステムを自作していたり、リスト以外の複雑な仮想化(例えば、巨大なカレンダーやグリッド)が必要な場合は、react-virtual が優れた選択肢となります。ヘッドレスであるため、コンポーネントの設計に縛られず、パフォーマンスとデザインを両立できます。

react-virtualizedreact-infinitereact-list については、既存プロジェクトのメンテナンス以外で新規に採用する理由はほとんどありません。これらは過去の技術的アプローチを反映しており、現代の React のパフォーマンス要件やエコシステムに最適化されていません。

技術選定では「できること」だけでなく「維持できること」も重要です。アクティブにメンテナンスされ、現代の Web 標準に沿ったライブラリを選ぶことが、長期的なプロジェクトの成功につながります。

選び方: react-list vs react-virtual vs react-infinite vs react-virtualized vs react-window vs react-window-infinite-loader

  • react-list:

    シンプルな実装で十分な場合や、非常に古いコードベースの維持が必要な場合に限り考慮します。機能性やパフォーマンス面で現代的な仮想化ライブラリに劣るため、新規開発では他の選択肢を優先してください。

  • react-virtual:

    フレームワークに依存しないロジック(ヘッドレス)を求めたり、Vue や Svelte などの他フレームワークとの共用を想定する場合に最適です。TanStack ecosystem との親和性が高く、カスタマイズ性を重視するプロジェクトに向いています。

  • react-infinite:

    レガシープロジェクトのメンテナンスを行う場合を除き、新規プロジェクトでの使用は推奨しません。メンテナンスが停滞しており、現代的な React のパターン(Hooks など)に準拠していないため、代替ライブラリへの移行を検討すべきです。

  • react-virtualized:

    既存のレガシーコードベースで既に導入されており、移行コストが大きい場合を除き、新規プロジェクトでは避けるべきです。react-window によって置き換えられた経緯があり、バンドルサイズや API の複雑さで劣ります。

  • react-window:

    React 環境で標準的な仮想化リストを実装する際のファーストチョイスです。Brian Vaughn によって維持されており、API がシンプルで軽量、かつパフォーマンスが優れています。ほとんどのユースケースでバランスが最も取れています。

  • react-window-infinite-loader:

    react-window を使用していて、さらに無限ローディング(スクロールに応じたデータ取得)機能が必要な場合に選択します。単体ではなく react-window と組み合わせて使用する専用コンポーネントです。

react-list のREADME

ReactList

A versatile infinite scroll React component.

Install

bower install react-list

# or

npm install react-list

ReactList depends on React.

Examples

Check out the example page and the the example page source for examples of different configurations.

Here's another simple example to get you started.

import loadAccount from 'my-account-loader';
import React from 'react';
import ReactList from 'react-list';

class MyComponent extends React.Component {
  state = {
    accounts: []
  };

  componentWillMount() {
    loadAccounts(::this.handleAccounts);
  }

  handleAccounts(accounts) {
    this.setState({accounts});
  }

  renderItem(index, key) {
    return <div key={key}>{this.state.accounts[index].name}</div>;
  }

  render() {
    return (
      <div>
        <h1>Accounts</h1>
        <div style={{overflow: 'auto', maxHeight: 400}}>
          <ReactList
            itemRenderer={::this.renderItem}
            length={this.state.accounts.length}
            type='uniform'
          />
        </div>
      </div>
    );
  }
}

Props

axis (defaults to y)

The axis that this list scrolls on.

initialIndex

An index to scroll to after mounting.

itemRenderer(index, key)

A function that receives an index and a key and returns the content to be rendered for the item at that index.

itemsRenderer(items, ref)

A function that receives the rendered items and a ref. By default this element is just a <div>. Generally it only needs to be overridden for use in a <table> or other special case. NOTE: You must set ref={ref} on the component that contains the items so the correct item sizing calculations can be made.

itemSizeEstimator(index, cache)

A function that receives an item index and the cached known item sizes and returns an estimated size (height for y-axis lists and width for x-axis lists) of that item at that index. This prop is only used when the prop type is set to variable and itemSizeGetter is not defined. Use this property when you can't know the exact size of an item before rendering it, but want it to take up space in the list regardless.

itemSizeGetter(index)

A function that receives an item index and returns the size (height for y-axis lists and width for x-axis lists) of that item at that index. This prop is only used when the prop type is set to variable.

length (defaults to 0)

The number of items in the list.

minSize (defaults to 1)

The minimum number of items to render at any given time. This can be used to render some amount of items initially when rendering HTML on the server.

pageSize (defaults to 10)

The number of items to batch up for new renders. Does not apply to 'uniform' lists as the optimal number of items is calculated automatically.

scrollParentGetter (defaults to finding the nearest scrollable parent)

A function that returns a DOM Element or Window that will be treated as the scrolling container for the list. In most cases this does not need to be set for the list to work as intended. It is exposed as a prop for more complicated uses where the scrolling container may not initially have an overflow property that enables scrolling.

scrollParentViewportSizeGetter (defaults to scrollParent's viewport size)

A function that returns the size of the scrollParent's viewport. Provide this prop if you can efficiently determine your scrollParent's viewport size as it can improve performance.

threshold (defaults to 100)

The number of pixels to buffer at the beginning and end of the rendered list items.

type (one of simple, variable, or uniform, defaults to simple)
  • simple This type is...simple. It will not cache item sizes or remove items that are above the viewport. This type is sufficient for many cases when the only requirement is incremental rendering when scrolling.

  • variable This type is preferred when the sizes of the items in the list vary. Supply the itemSizeGetter when possible so the entire length of the list can be established beforehand. Otherwise, the item sizes will be cached as they are rendered so that items that are above the viewport can be removed as the list is scrolled.

  • uniform This type is preferred when you can guarantee all of your elements will be the same size. The advantage here is that the size of the entire list can be calculated ahead of time and only enough items to fill the viewport ever need to be drawn. The size of the first item will be used to infer the size of every other item. Multiple items per row are also supported with this type.

useStaticSize (defaults to false)

Set to true if the item size will never change (as a result of responsive layout changing or otherwise). This prop is only used when the prop type is set to uniform. This is an opt-in optimization that will cause the very first element's size to be used for all elements for the duration of the component's life.

useTranslate3d (defaults to false)

A boolean to determine whether the translate3d CSS property should be used for positioning instead of the default translate. This can help performance on mobile devices, but is supported by fewer browsers.

Methods

scrollTo(index)

Put the element at index at the top of the viewport. Note that if you aren't using type='uniform' or an itemSizeGetter, you will only be able to scroll to an element that has already been rendered.

scrollAround(index)

Scroll the viewport so that the element at index is visible, but not necessarily at the top. The scrollTo note above also applies to this method.

getVisibleRange() => [firstIndex, lastIndex]

Return the indices of the first and last items that are at all visible in the viewport.

FAQ

What is "ReactList failed to reach a stable state."?

This happens when specifying the uniform type without actually providing uniform size elements. The component attempts to draw only the minimum necessary elements at one time and that minimum element calculation is based off the first element in the list. When the first element does not match the other elements, the calculation will be wrong and the component will never be able to fully resolve the ideal necessary elements.

Why doesn't it work with margins?

The calculations to figure out element positioning and size get significantly more complicated with margins, so they are not supported. Use a transparent border or padding or an element with nested elements to achieve the desired spacing.

Why is there no onScroll event handler?

If you need an onScroll handler, just add the handler to the div wrapping your ReactList component:

<div style={{height: 300, overflow: 'auto'}} onScroll={this.handleScroll}>
  <ReactList ... />
</div>

Development

open docs/index.html
make