mcpField
R
react-virtuoso

react-virtuoso

github.com/virtuoso-dev開発1213 回インストール2 日前に更新
リポジトリ

react-virtuoso について

react-virtuoso は、React 向けの仮想化リスト、グリッド、テーブル用コンポーネントライブラリです。表示範囲だけを描画し、ResizeObserver で可変サイズの項目も自動計測するため、大量データを扱う画面に適しています。

主な機能

  1. 01リスト、グループリスト、テーブル、グリッドの仮想化
  2. 02可変サイズ項目の自動計測
  3. 03無限スクロールと先頭への項目追加
  4. 04プログラムによるスクロールと状態復元
  5. 05ウィンドウスクロールとカスタムスクロール親への対応

活用シーン

  1. 01大量の項目を含むリストやテーブルの描画
  2. 02固定グループヘッダー付きリストの構築
  3. 03レスポンシブな複数列仮想グリッドの作成
  4. 04古い項目の読み込み時にスクロール位置を維持

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/virtuoso-dev/skills --skill react-virtuoso

インストール後、Claude Code で/react-virtuosoを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/1.virtuoso/
auto-resizing.md
basic-usage.md
custom-scroll-container.md
customize-rendering.md
endless-scrolling.md
footer.md
horizontal-mode.md
initial-index.md
keyboard-navigation.md
press-to-load-more.md
range-change-callback.md
scroll-handling.md
scroll-seek-placeholders.md
scroll-to-index.md
tailwind-test.md
top-items.md
window-scrolling.md
references/2.grouped-virtuoso/
grouped-by-first-letter.md
grouped-numbers.md
grouped-with-load-on-demand.md
scroll-to-group.md
references/3.virtuoso-grid/
grid-responsive-columns.md
references/4.table-virtuoso/
basic-table.md
table-fixed-columns.md
table-fixed-headers.md
table-grouped.md
references/5.third-party-integration/
material-ui-endless-scrolling.md
mocking-in-tests.md
mui-table-virtual-scroll.md
tanstack-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/ にあります。

API リファレンスの全文はこちらです:https://virtuoso.dev/react-virtuoso/

コメント