# 2026年版 高速JavaScript ListView: ウィンドウイング、仮想化、パフォーマンス最適化 > TanStack Virtual、react-virtuoso、react-windowを使用したJavaScriptリスト仮想化を習得します。ウィンドウイング技術で10万件以上のアイテムを60fpsでレンダリングするコード例を解説します。 - Published: 2026-09-15 - Updated: 2026-09-15 - Author: Anthony Fillion-Maillet - Tags: javascript, performance, virtualization, react, typescript - Reading time: 9 min --- 仮想化を行わずにJavaScript ListViewで数千件のアイテムをレンダリングすると、ブラウザは数千のDOMノードを作成します。その結果、初期レンダリングの遅延、スクロールのカクつき、メモリ消費の増大が発生し、モバイルデバイスではクラッシュすることもあります。2026年における高速JavaScript ListView実装では、ウィンドウイングによってこの問題を解決します。ビューポートに表示される10〜20件のアイテムのみをレンダリングし、全アイテムを含むスクロール可能な領域をシミュレートする手法です。 > **ウィンドウイングの本質** > > ウィンドウイングは、表示されているアイテムと小さなバッファ(オーバースキャン)のみをレンダリングし、リストサイズに関係なくDOMノードを10,000から約20に削減します。 ## 大規模リストでブラウザパフォーマンスが低下する理由 すべてのDOMノードはメモリを消費し、レイアウト計算時にブラウザがその位置を追跡する必要があります。適度に複雑なマークアップ(アバター、2つのテキストスパン、ボタン)を持つ10,000件のリストは、40,000以上のDOMノードを作成します。Chromeのレンダリングエンジンは、スクロールイベントのたびにそれらすべてのスタイルと位置を再計算します。 その結果、初期レンダリングに2〜4秒かかり、スクロールは15fpsまで低下し、メモリ使用量は500MB以上に増加します。モバイルSafariでは、タブがクラッシュすることも珍しくありません。 仮想化は、一定のDOMノード数を維持することでこの問題を解決します。データ配列に100件含まれていても100,000件含まれていても、レンダリングされるDOMは約20〜30ノードに留まり、メモリは一定でスクロールパフォーマンスは60fpsを維持します。 ```typescript // VirtualizedList.tsx import { useVirtualizer } from '@tanstack/react-virtual' import { useRef } from 'react' interface Item { id: string name: string email: string } export function VirtualizedList({ items }: { items: Item[] }) { const parentRef = useRef(null) // 推定行高でバーチャライザーインスタンスを作成 const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, estimateSize: () => 60, // ピクセル単位の推定高さ overscan: 5, // ビューポートの上下に5つの追加アイテムをレンダリング }) return (
{virtualizer.getVirtualItems().map((virtualRow) => { const item = items[virtualRow.index] return (

{item.name}

{item.email}

) })}
) } ``` この実装は、表示される行とオーバースキャンバッファのみをレンダリングします。外側のコンテナがスクロール可能な総高さを設定し、すべてのアイテムがDOMに存在するかのようにスクロールバーを動作させます。 ## TanStack Virtual: 2026年のヘッドレスアプローチ [TanStack Virtual](https://tanstack.com/virtual/latest)は、フレームワークに依存しない仮想化プリミティブを提供します。react-windowやreact-virtuosoとは異なり、事前構築されたコンポーネントは提供せず、どのアイテムをレンダリングし、どこに配置するかを計算するフックのみを提供します。このヘッドレスアーキテクチャにより、スタイリングとDOM構造を完全に制御できます。 現在のバージョン(3.13)は、React、Vue、Solid、Svelte、およびバニラJavaScriptをサポートしています。主な設定オプションは以下の通りです。 | オプション | 目的 | デフォルト | |--------|---------|--------| | `count` | アイテムの総数 | 必須 | | `estimateSize` | 推定アイテム高さ/幅を返す関数 | 必須 | | `overscan` | ビューポート外にレンダリングする追加アイテム数 | 1 | | `getScrollElement` | スクロール可能なコンテナを返す | 必須 | | `measureElement` | 動的サイズ用のカスタム測定関数 | 自動 | `overscan`の値はスクロールの滑らかさに直接影響します。低すぎる値(0〜2)に設定すると、高速スクロール時に白いフラッシュが発生します。高すぎる値(20以上)に設定すると、仮想化のメリットが失われます。ほとんどのアプリケーションでは、5〜10が最適なバランスを提供します。 ```typescript // DynamicHeightList.tsx import { useVirtualizer } from '@tanstack/react-virtual' import { useRef, useCallback } from 'react' interface Message { id: string content: string timestamp: number } export function ChatList({ messages }: { messages: Message[] }) { const parentRef = useRef(null) // measureElementで動的な高さ測定を有効化 const virtualizer = useVirtualizer({ count: messages.length, getScrollElement: () => parentRef.current, estimateSize: () => 80, // 大まかな推定、実際は後で測定 overscan: 8, }) return (
{virtualizer.getVirtualItems().map((virtualRow) => { const message = messages[virtualRow.index] return (

{new Date(message.timestamp).toLocaleTimeString()}

{message.content}

) })}
) } ``` `measureElement` refは、レンダリング後の自動高さ測定を有効にします。これにより、手動で高さを計算することなく、可変コンテンツ長を処理できます。 ## react-virtuoso: 機能豊富な代替選択肢 [react-virtuoso](https://virtuoso.dev/)は正反対のアプローチを取ります。バッテリー同梱型のコンポーネントライブラリです。TanStack Virtualがスクロールコンテナと配置ロジックの構築を必要とするのに対し、react-virtuosoはこれらを内部で処理します。 トレードオフはバンドルサイズ(TanStack Virtualの12KBに対して37KB)ですが、グループ化されたリスト、スティッキーヘッダー、逆スクロール付きチャットインターフェースなどの一般的なパターンに対するボイラープレートが大幅に削減されます。 ```typescript // VirtuosoList.tsx import { Virtuoso } from 'react-virtuoso' interface User { id: string name: string department: string } export function UserDirectory({ users }: { users: User[] }) { return ( (
{user.name.charAt(0)}

{user.name}

{user.department}

)} /> ) } ``` より少ないコードで同じ仮想化結果を達成できます。react-virtuosoは自動的にアイテムを測定し、動的な高さを処理し、スクロール位置の永続化を提供します。 [Reactパフォーマンスに関する面接準備](/technologies/react-next/interview-questions/styling-css-in-js)において、各ライブラリをいつ選択すべきかを理解していることは、アーキテクチャへの認識を示す機会となります。TanStack Virtualは、既にTanStack Tableを使用しているアプリケーションや、フレームワークに依存しないコードが必要な場合に適しています。react-virtuosoは、バンドルサイズよりも開発速度を優先するチームに適しています。 ## 一般的な落とし穴と解決策 ### 画像によるスクロール位置のジャンプ 画像は非同期で読み込まれ、初期測定後に行の高さが変化します。バーチャライザーはこの変化を予測できず、下のコンテンツがずれてしまいます。 解決策は、aspect-ratioまたは固定寸法を使用して画像のスペースを予約することです。 ```typescript // ImageListItem.tsx interface ImageItem { id: string src: string title: string } export function ImageListItem({ item }: { item: ImageItem }) { return (
{/* 読み込み状態に関係なく80x80pxのスペースを予約 */}
{item.title}

{item.title}

) } ``` ### スクロール中の状態消失 仮想化されたアイテムは、ビューからスクロールアウトするとアンマウントされます。ローカル状態(フォーム入力、展開されたアコーディオン)は消えてしまいます。 解決策は、アイテムIDでキー付けされた状態を親コンポーネントまたは状態管理ライブラリに持ち上げることです。 ```typescript // ExpandableList.tsx import { useState, useCallback } from 'react' import { useVirtualizer } from '@tanstack/react-virtual' interface FAQ { id: string question: string answer: string } export function FAQList({ faqs }: { faqs: FAQ[] }) { // 状態は親に存在し、行のアンマウント後も保持される const [expandedIds, setExpandedIds] = useState>(new Set()) const parentRef = useRef(null) const toggleExpanded = useCallback((id: string) => { setExpandedIds(prev => { const next = new Set(prev) if (next.has(id)) { next.delete(id) } else { next.add(id) } return next }) }, []) const virtualizer = useVirtualizer({ count: faqs.length, getScrollElement: () => parentRef.current, estimateSize: () => 60, overscan: 5, }) return (
{virtualizer.getVirtualItems().map((virtualRow) => { const faq = faqs[virtualRow.index] const isExpanded = expandedIds.has(faq.id) return (
) })}
) } ``` ### 不安定なキーによる再レンダリング 配列インデックスをキーとして使用すると、リストが並び替えられるまでは機能します。アイテムが間違った状態を受け取り、視覚的な不具合や無駄なレンダリングが発生します。 常にデータ自体から安定した一意の識別子を使用する必要があります。 ```typescript // 正しい: データからの安定したキー {virtualizer.getVirtualItems().map((virtualRow) => { const item = items[virtualRow.index] return
...
// データベース/APIからのid })} // 間違い: インデックスはリストの並び替えで変化する {virtualizer.getVirtualItems().map((virtualRow, index) => { return
...
// 並び替え時にバグを引き起こす })} ``` ## 2026年プロジェクト向けライブラリ比較 適切な仮想化ライブラリの選択は、プロジェクト要件によって異なります。この比較は2026年のエコシステムを反映しています。 | 基準 | TanStack Virtual | react-virtuoso | react-window | |----------|-----------------|----------------|---------------| | バンドルサイズ | 12KB | 37KB | 6KB | | 動的な高さ | 手動設定 | 自動 | 非対応 | | TypeScript | ネイティブ | ネイティブ | @typesパッケージ | | フレームワークサポート | React, Vue, Solid, Svelte | Reactのみ | Reactのみ | | メンテナンス | アクティブ(2026年) | アクティブ(2026年) | メンテナンスモード | | 学習コスト | 高い | 低い | 最も低い | react-windowは、バンドルサイズが重要な単純な固定高さリストには依然として有効です。ただし、メンテナンスモードのステータスと動的な高さサポートの欠如により、複雑な要件を持つ新規プロジェクトには不向きです。 [パフォーマンスの高いReactアプリケーションの構築](/blog/react-next/react-testing-2026-vitest-rtl-best-practices)において、仮想化のトレードオフを理解することは、面接官が求めるパフォーマンス意識を示します。 ## パフォーマンス改善の測定 仮想化のメリットを定量化するには、一貫した測定方法論が必要です。Chrome DevToolsのPerformanceパネルが関連するメトリクスを提供します。 仮想化前後の主要な測定項目は以下の通りです。 - **First Contentful Paint (FCP)**: 最初のコンテンツがレンダリングされるまでの時間。仮想化は初期DOM作成を制限することでこれを短縮します。 - **Total Blocking Time (TBT)**: 読み込み中のメインスレッドブロッキング。DOMノードが少ないほど、スタイル/レイアウト計算が少なくなります。 - **メモリ使用量**: Memoryパネルでヒープサイズのスナップショットを取得。リスト長に関係なく一定であるべきです。 - **スクロールfps**: スクロール中にPerformanceトレースを記録。フレームレートは60fpsを維持する必要があります。 ```typescript // PerformanceMonitor.tsx import { useEffect, useRef } from 'react' export function useScrollFPS(containerRef: React.RefObject) { const frameCountRef = useRef(0) const lastTimeRef = useRef(performance.now()) useEffect(() => { const container = containerRef.current if (!container) return let rafId: number const measureFrame = () => { frameCountRef.current++ const now = performance.now() if (now - lastTimeRef.current >= 1000) { console.log(`Scroll FPS: ${frameCountRef.current}`) frameCountRef.current = 0 lastTimeRef.current = now } rafId = requestAnimationFrame(measureFrame) } const handleScroll = () => { if (!rafId) { rafId = requestAnimationFrame(measureFrame) } } container.addEventListener('scroll', handleScroll, { passive: true }) return () => { container.removeEventListener('scroll', handleScroll) cancelAnimationFrame(rafId) } }, [containerRef]) } ``` このフックは開発中にスクロールFPSをログ出力します。本番アプリケーションでは、Sentry Performanceやカスタムテレメトリなどの監視ツールを使用する必要があります。 ## JavaScriptリストパフォーマンスの重要なポイント - 仮想化はDOMノードを数千から約20に削減し、データサイズに関係なくメモリとスクロールパフォーマンスを一定に保ちます - TanStack Virtual(12KB)は、React、Vue、Solid、Svelteで最大限の制御を可能にするヘッドレスプリミティブを提供します - react-virtuoso(37KB)は、自動動的高さ処理付きのバッテリー同梱型コンポーネントを提供します - react-windowは2022年以降メンテナンスモードであり、動的な高さサポートがありません - オーバースキャンは5〜10に設定すると、仮想化のメリットを損なうことなく最適なスクロールの滑らかさが得られます - 画像の寸法を予約して、非同期読み込み中のスクロール位置ジャンプを防止します - 仮想化された行はスクロール中にアンマウントされるため、展開可能/編集可能な状態は親コンポーネントに持ち上げます - 並び替え時の状態の不一致を防ぐため、配列インデックスではなくデータIDからの安定したキーを使用します - 実装前後でFCP、TBT、メモリ、スクロールFPSを測定し、改善を定量化します --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/node-nestjs/fast-javascript-listview-2026-windowing-virtualization-performance