react-virtuoso について
react-virtuoso は、React 向けの仮想化リスト、グリッド、テーブル用コンポーネントライブラリです。表示範囲だけを描画し、ResizeObserver で可変サイズの項目も自動計測するため、大量データを扱う画面に適しています。
主な機能
- 01リスト、グループリスト、テーブル、グリッドの仮想化
- 02可変サイズ項目の自動計測
- 03無限スクロールと先頭への項目追加
- 04プログラムによるスクロールと状態復元
- 05ウィンドウスクロールとカスタムスクロール親への対応
活用シーン
- 01大量の項目を含むリストやテーブルの描画
- 02固定グループヘッダー付きリストの構築
- 03レスポンシブな複数列仮想グリッドの作成
- 04古い項目の読み込み時にスクロール位置を維持
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ virtuoso-dev/ skills --skill react-virtuosoインストール後、Claude Code で/react-virtuosoを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/1.virtuoso/auto-resizing.mdbasic-usage.mdcustom-scroll-container.mdcustomize-rendering.mdendless-scrolling.mdfooter.mdhorizontal-mode.mdinitial-index.mdkeyboard-navigation.mdpress-to-load-more.mdrange-change-callback.mdscroll-handling.mdscroll-seek-placeholders.mdscroll-to-index.mdtailwind-test.mdtop-items.mdwindow-scrolling.mdreferences/2.grouped-virtuoso/grouped-by-first-letter.mdgrouped-numbers.mdgrouped-with-load-on-demand.mdscroll-to-group.mdreferences/3.virtuoso-grid/grid-responsive-columns.mdreferences/4.table-virtuoso/basic-table.mdtable-fixed-columns.mdtable-fixed-headers.mdtable-grouped.mdreferences/5.third-party-integration/material-ui-endless-scrolling.mdmocking-in-tests.mdmui-table-virtual-scroll.mdtanstack-table-integration.md詳細
react-virtuoso
react-virtuoso は、大きなリスト、グリッド、テーブルのうち、表示されている部分だけをレンダリングします。ResizeObserver を使って項目のサイズを自動的に測定します。項目の高さが可変でも、サイズを設定せずにそのまま動作します。
import { Virtuoso } from 'react-virtuoso'
;<Virtuoso style={{ height: '100%' }} data={users} itemContent={(index, user) => <div>{user.name}</div>} />
適切なコンポーネントの選び方
| 必要なもの | 使用するもの |
|---|---|
| 高さが可変または固定のフラットリスト | Virtuoso |
| 固定グループヘッダー付きのグループ | GroupedVirtuoso |
| 行を仮想化した HTML テーブル | TableVirtuoso |
| グループ化された行と固定グループヘッダーを備えたテーブル | GroupedTableVirtuoso |
| 同じサイズの項目をレスポンシブに複数列で表示するグリッド | VirtuosoGrid |
| チャット/AI 会話 UI(ストリーミング、最下部への固定、履歴の先頭への追加) | @virtuoso.dev/message-list — 代わりに message-list スキルを使用してください |
| 列、並べ替え、フィルタリング、列機能を備えたデータグリッド | @virtuoso.dev/data-table — 代わりに data-table スキルを使用してください |
すべてのコンポーネントで、共通の基本プロパティ(data/totalCount、itemContent、components、スクロールコールバック、ref メソッド)を使用できます。
不要なこと
TanStack Virtual や react-window とは異なり、react-virtuoso は項目を自動的に測定します。これらのライブラリで使うパターンを持ち込まないでください。
estimateSize、measureElement、data-indexの設定は不要です。測定は自動で行われます。- 項目に絶対位置指定や
transform: translateYを適用しないでください。項目の配置はライブラリが行います。 itemSizeを固定する必要はありません。デフォルトで高さは可変です。項目の高さが実際にすべて同じ場合に限り、パフォーマンス最適化としてfixedItemHeightを渡してください。- ウィンドウ化の計算は不要です。
data(またはtotalCount)を渡し、itemContentで項目をレンダリングしてください。
基本ルール
- コンポーネントには高さが必要です。
style={{ height: '100%' }}(高さを設定した親要素と併用)または固定の高さを設定してください。高さが 0 のコンテナーには何もレンダリングされません。 - 項目に CSS のマージンを設定しないでください。 ResizeObserver が報告する
contentRectにはマージンが含まれないため、計算される合計の高さが不足します。その典型的な症状は、リストを一番下までスクロールできないことです。代わりにパディングを使用してください。<p>、見出し、<ul>、<blockquote>、<pre>のデフォルトのマージンにも注意してください。 - 項目がある場合は
totalCountではなくdataを使用してください。dataを使うと、itemContent={(index, item) => ...}で項目を受け取れます。totalCountは項目がインデックスから導出される場合にのみ使用してください。更新時には新しい配列参照を生成する必要があります。 - リストの先頭への追加、並べ替え、フィルタリングが行われる場合は、必ず
computeItemKey={(index, item) => item.id}を指定してください。 デフォルトのキーはインデックスです。位置が変わると項目が再マウントされ、状態が失われます。 componentsのオーバーライドはレンダー関数の外で定義してください。 インラインで定義すると、レンダーのたびに新しいコンポーネント型が作られ、スクロールするたびにサブツリーが再マウントされます。ScrollerとListのオーバーライドでは、DOM 要素にrefを転送する必要があります。- 項目のコンテンツで、高さ 0 の要素をレンダリングしないでください。 「zero-sized element, this should not happen」というエラーは、項目が 0px と測定されたことを意味します。代わりに、データから空の項目を除外してください。
よくあるパターン
無限スクロール
<Virtuoso data={items} endReached={() => loadMore()} itemContent={(index, item) => <Item item={item} />} />
endReached は最下部で呼び出されます。components.Footer を使ってスピナーを表示してください。クリックで「さらに読み込む」場合は、ボタンを Footer に配置してください。
項目の先頭への追加(逆方向の無限スクロール)
単純に先頭へ追加すると、リストがジャンプします。代わりに、firstItemIndex を追加した項目数だけ減らします。
const [firstItemIndex, setFirstItemIndex] = useState(START)
const prepend = async () => {
const older = await fetchOlderItems()
setFirstItemIndex((i) => i - older.length)
setItems((current) => [...older, ...current])
}
<Virtuoso
computeItemKey={(_, item) => item.id}
data={items}
firstItemIndex={firstItemIndex}
initialTopMostItemIndex={{ index: 'LAST' }}
startReached={() => void prepend()}
...
/>
firstItemIndex は正の数である必要があるため、大きな値(例:100000)から始めてください。GroupedVirtuoso の場合は、グループヘッダーを除き、新しい項目の数だけ減らします。endless-scrolling を参照してください。
新しい項目への追従(ログ、フィード)
<Virtuoso followOutput="smooth" data={messages} ... />
followOutput は、ユーザーがすでに最下部にいる場合にのみ、新しく追加された最下部の項目までスクロールします。'auto' | 'smooth' | false、またはカスタムロジック用の関数 (isAtBottom) => ... を指定できます。完全なチャット UI には、@virtuoso.dev/message-list をおすすめします。
プログラムによるスクロール
const ref = useRef<VirtuosoHandle>(null)
ref.current?.scrollToIndex({ index: 500, align: 'center', behavior: 'smooth' })
ハンドルには、scrollIntoView(表示されていない場合にのみスクロールするため、キーボード操作に適しています)、scrollTo/scrollBy(ピクセル単位)、getState(再マウント時、たとえば戻るナビゲーションで restoreStateFrom に渡すスナップショット)もあります。
特定の項目から表示を開始するには、initialTopMostItemIndex={{ index, align: 'start' }} を使用してください。最初に最上部を描画してからジャンプする initialScrollTop は使わないでください。
グループ化されたリスト
<GroupedVirtuoso
groupCounts={[20, 30]} // items per group, in order
groupContent={(groupIndex) => <Header group={groups[groupIndex]} />}
itemContent={(index, groupIndex) => <Item item={items[index]} />} // index is absolute across all items
/>
フラットなデータと groupCounts を渡し、インデックスからデータへの対応付けはご自身で行います。
テーブル
<TableVirtuoso
data={rows}
fixedHeaderContent={() => (
<tr>
<th>Name</th>
</tr>
)}
itemContent={(index, row) => (
<>
<td>{row.name}</td>
</>
)} // <td> cells only — the row <tr> is rendered for you
/>
テーブルに border-collapse: collapse を設定しないでください。スティッキーヘッダーの境界線が本文と一緒にスクロールしてしまいます。明示的なセル境界線を指定した border-collapse: separate を使用してください。components(Table、TableRow、TableHead、TableBody)で構造をカスタマイズできます。
グリッド
VirtuosoGrid は、同じサイズの項目を列に並べて仮想化します。列数は CSS で制御します。components.Item にパーセンテージ幅(3 列なら 33%。メディアクエリで変更)を指定し、components.List には display: flex; flex-wrap: wrap を指定してください。grid-responsive-columns を参照してください。
ページレベルのスクロール
useWindowScroll を使用してリストをドキュメントのスクロールに連動させるか、customScrollParent={element} を指定して既存のスクロール可能な祖先要素に接続します。スクロールモードは必ずどちらか一方を選んでください。両方を組み合わせると、両方のコンテナーがスクロールします。
テスト
JSDOM にはレイアウト機能がないため、測定値をモックしないと、Jest/Vitest では項目が描画されません。
render(<Virtuoso data={data} />, {
wrapper: ({ children }) => (
<VirtuosoMockContext.Provider value={{ viewportHeight: 300, itemHeight: 100 }}>{children}</VirtuosoMockContext.Provider>
),
})
グリッドには VirtuosoGridMockContext を使用してください(viewportWidth と itemWidth を追加します)。スクロール動作のテストには、実ブラウザーでのテスト(Playwright)をおすすめします。
トラブルシューティング
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
| 何も表示されない | コンテナーの高さが 0 | コンポーネントまたはその親に実際の高さを設定します |
| 最後の項目までスクロールできない/下部付近でジャンプする | 項目コンテンツのマージン | マージンをパディングに置き換えます |
| スクロール中に項目がちらつく、または状態が失われる | インラインの components 定義、またはインデックスベースのキー | コンポーネントを外部に定義し、computeItemKey を追加します |
| 項目を先頭に追加するとリストがジャンプする | firstItemIndex を調整せずに項目を追加している | firstItemIndex を使った先頭追加パターンを使用します |
| "zero-sized element, this should not happen" | 高さ 0 で項目がレンダリングされた | データから空の項目を除外します |
| 開発時に「ResizeObserver loop completed with undelivered notifications」のオーバーレイが表示される | ResizeObserver の無害なタイミング問題が開発サーバーのオーバーレイに表示されている | webpack/vite のオーバーレイ設定で runtimeErrors を無効にします。エラートラッキングで安全にフィルタリングできます |
| サイズの挙動が説明しにくい | — | logLevel={LogLevel.DEBUG} を設定し、すべてのレベルを有効にしてコンソールを確認します |
調整:increaseViewportBy はビューポートの外側に余分なピクセルをレンダリングします(より滑らかになりますが、DOM が増えます)。defaultItemHeight は初回のプローブレンダリングを省略します。fixedItemHeight は測定を完全に省略します(項目の高さが均一な場合のみ)。scrollSeekConfiguration は高速スクロール中に項目をプレースホルダーに置き換えます。
参考資料
コードをすべて含む詳細なガイドは references/ にあります。
- references/README.md — パッケージの概要と全機能一覧
references/1.virtuoso/— フラットリストのガイド:basic-usage、endless-scrolling、press-to-load-more、initial-index、scroll-to-index、keyboard-navigation、customize-rendering、footer、top-items、scroll-handling、range-change-callback、scroll-seek-placeholders、auto-resizing、window-scrolling、custom-scroll-container、horizontal-modereferences/2.grouped-virtuoso/— グループ化リスト:grouped-numbers、grouped-by-first-letter、scroll-to-group、grouped-with-load-on-demandreferences/3.virtuoso-grid/— grid-responsive-columnsreferences/4.table-virtuoso/— テーブル:basic-table、table-fixed-headers、table-fixed-columns、table-groupedreferences/5.third-party-integration/— mocking-in-tests、tanstack-table-integration、mui-table-virtual-scroll、material-ui-endless-scrolling- references/6.troubleshooting.md — トラブルシューティングの完全ガイド
API リファレンスの全文はこちらです:https://virtuoso.dev/react-virtuoso/