😄

TanStack Virtual v3(ドキュメント理解) その1

2024/05/29に公開

概要

大量のデータやコンポーネント が表示される状況で 実際に画面 に表示されている要素だけを DOMにレンダリングする技術を指す。

具体的な利用例としてはスクロール可能なリストやテーブルを表示するような局面が当てはまる。
大量のアイテム(例えば何千ものリスト項目や行)を表示する必要がある場合、すべてのアイテムをDOMにレンダリングするとパフォーマンスに悪影響を及ぼすことがあるためこのライブラリを導入することで 解決できる。

具体的にこのライブラリがどのようなことを行っているのかというと仮想化を行っている。
仮想化とは、現在スクロール位置に基づいて、ユーザーに見えているアイテムのみをDOMにレンダリングします。スクロールすると、新しく見えるようになるアイテムが動的に追加され、スクロールアウトしたアイテムがDOMから削除されます。

Virtualizerとは?

TanStack Virtualの中心となるのがVirtualizerです。Virtualizerは、垂直(デフォルト)または水平の軸に配置することができます。この機能を利用することで、垂直、水平、さらにはグリッドのような仮想化を実現できます。

reactでの使用例

import { useVirtualizer } from '@tanstack/react-virtual';

function App() {
  // スクロール可能な要素の参照を作成
  const parentRef = React.useRef()

  // Virtualizerの設定
  const rowVirtualizer = useVirtualizer({
    count: 10000, // リストの要素数
    getScrollElement: () => parentRef.current, // スクロールする要素を取得
    estimateSize: () => 35, // 各要素の高さを推定
  })

  return (
    <>
      {/* スクロール可能な要素 */}
      <div
        ref={parentRef}
        style={{
          height: '400px',
          overflow: 'auto', // スクロールを有効にする
        }}
      >
        {/* リストの全要素を保持する大きな内部要素 */}
        <div
          style={{
            height: `${rowVirtualizer.getTotalSize()}px`,
            width: '100%',
            position: 'relative',
          }}
        >
          {/* Virtualizer内の表示可能なアイテムのみを表示 */}
          {rowVirtualizer.getVirtualItems().map((virtualItem) => (
            <div
              key={virtualItem.key}
              style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                height: `${virtualItem.size}px`,
                transform: `translateY(${virtualItem.start}px)`,
              }}
            >
              Row {virtualItem.index}
            </div>
          ))}
        </div>
      </div>
    </>
  )
}

各オプションの詳細

1. count

  • 意味: リストの総要素数を指定します。
  • : count: 10000は、リストに10,000個の要素があることを示しています。
  • 詳細: 例えば、長いリストをスクロールするとき、すべての要素を一度にレンダリングするのは非効率です。countを指定することで、どのくらいの要素がリストに含まれているかをVirtualizerに知らせます。

2. getScrollElement

  • 意味: スクロール可能な要素を取得するための関数です。
  • : getScrollElement: () => parentRef.currentは、parentRefというReactのrefを使ってスクロール可能な要素を取得しています。
  • 詳細: この関数は、Virtualizerに対してどの要素がスクロール可能な親要素であるかを知らせます。これにより、Virtualizerはスクロール位置を追跡し、適切な要素のみをレンダリングします。

3. estimateSize

  • 意味: 各要素の高さ(または幅)を推定するための関数です。
  • : estimateSize: () => 35は、各要素の高さが約35ピクセルであることを示しています。
  • 詳細: 要素の正確なサイズを事前に知ることは難しい場合があります。この関数を使って、平均的なサイズをVirtualizerに知らせることで、スクロール位置に基づいてレンダリングする要素を計算します。

これらはライブラリに対して適切 に動作するように 予測可能な情報 を事前に提供することで効率的に 仮想化を実現しています。

React Virtualとは?

@tanstack/react-virtualは、Reactアプリケーションで仮想化を簡単に実現するためのアダプターです。このアダプターは、コアの仮想化ロジックをラップし、Reactで使いやすい形にしています。

useVirtualizer

useVirtualizerは、標準的なVirtualizerインスタンスを返す関数です。これは、指定したHTML要素をスクロール要素(scrollElement)として機能させるように設定されています。

useWindowVirtualizer

useWindowVirtualizerは、ウィンドウをスクロール要素として使うためのVirtualizerインスタンスを返す関数です。

Virtualizer

Virtualizerクラスは、TanStack Virtualの中心的な部分で、リスト内のアイテムを効率的に表示するための主要な機能を提供します。通常、Virtualizerのインスタンスは、フレームワークアダプター(例えばReactやVue用のアダプター)によって自動的に作成されますが、直接操作することも可能です。

必須オプション

count

count: number

意味: 仮想化するアイテムの総数。
なぜ必要か: リスト内のアイテムの総数をVirtualizerに知らせることで、全体のレイアウトを計算し、適切にアイテムをレンダリングするために必要です。

getScrollElement

getScrollElement: () => TScrollElement

意味: 仮想化のためのスクロール可能な要素を返す関数。
なぜ必要か: スクロール可能な要素を特定することで、Virtualizerがスクロール位置を追跡し、表示すべきアイテムを決定できるようにします。スクロール要素がまだ利用できない場合は、nullを返すことで対応します。

estimateSize

estimateSize: (index: number) => number

意味: 各アイテムのサイズを推定する関数。
なぜ必要か: アイテムのサイズを事前に推定することで、スクロール位置に基づいて表示すべきアイテムを効率的に計算できます。これは特に動的なサイズ変更がある場合に重要です。

オプション

debug

debug?: boolean

意味: デバッグログを有効にするかどうか。
なぜ必要か: Virtualizerの動作をデバッグするために、内部の動作をログとして出力することで問題の特定と解決が容易になります。

initialRect

initialRect?: Rect

意味: スクロール要素の初期の矩形。SSR環境で有用。
なぜ必要か: サーバーサイドレンダリング(SSR)環境では、初期の要素のサイズと位置を指定することで、クライアントサイドで再レンダリングされる前に正確なレイアウトを提供できます。
onChange

onChange?: (instance: Virtualizer<TScrollElement, TItemElement>) => void

意味: Virtualizerの内部状態が変更されたときに呼ばれるコールバック関数。
なぜ必要か: Virtualizerの状態が変更されたときに特定の処理を実行したい場合に、コールバックを使ってカスタムロジックを追加できます。

overscan

overscan?: number

意味: 表示領域の上下にレンダリングするアイテムの数。
なぜ必要か: スクロール時に空白が見えないようにするために、表示領域の上下に追加のアイテムをレンダリングします。これにより、スムーズなスクロールが確保されます。

horizontal

horizontal?: boolean

意味: Virtualizerを水平に設定するかどうか。
なぜ必要か: 水平スクロールのリストを仮想化する場合に、このオプションを設定することで、適切にアイテムを配置できます。

paddingStart / paddingEnd

paddingStart?: number

paddingEnd?: number

意味: Virtualizerの開始位置と終了位置に適用するパディング(ピクセル)。
なぜ必要か: リストの先頭や末尾に余白を追加することで、視覚的なスペースを調整できます。

scrollPaddingStart / scrollPaddingEnd

scrollPaddingStart?: number

scrollPaddingEnd?: number

意味: スクロール時に要素の開始位置と終了位置に適用するパディング(ピクセル)。
なぜ必要か: スクロール時の見た目や動作を調整するために、追加のパディングを設定することができます。

initialOffset

initialOffset?: number | (() => number)

意味: Virtualizerに適用する初期オフセット。
なぜ必要か: 初期スクロール位置を指定することで、特定の位置からリストを表示したい場合に役立ちます。特にSSR環境で有用です。

getItemKey

getItemKey?: (index: number) => Key

意味: 各アイテムのインデックスを受け取り、一意のキーを返す関数。
なぜ必要か: 各アイテムに一意のキーを与えることで、Reactがアイテムの変更を効率的に管理できます。

rangeExtractor

rangeExtractor?: (range: Range) => number[]

意味: 表示範囲のインデックスを受け取り、レンダリングするインデックスの配列を返す関数。
なぜ必要か: 必要に応じて特定のアイテムを追加または削除する場合に、カスタムの範囲抽出ロジックを実装できます。

scrollToFn

scrollToFn?: (
offset: number,
  options: { adjustments?: number; behavior?: 'auto' | 'smooth' },
  instance: Virtualizer<TScrollElement, TItemElement>,
) => void

意味: スクロール動作を実装するための関数。
なぜ必要か: カスタムのスクロール動作を実装したい場合に、この関数を使って具体的なスクロールロジックを定義できます。

observeElementRect

observeElementRect: (
instance: Virtualizer<TScrollElement, TItemElement>,
  cb: (rect: Rect) => void,
) => void | (() => void)

意味: スクロール要素の矩形を監視する関数。
なぜ必要か: スクロール要素のサイズ変更を監視し、それに応じて仮想化の計算を調整するために使用します。

observeElementOffset

observeElementOffset: (
instance: Virtualizer<TScrollElement, TItemElement>,
    cb: (offset: number) => void,
  ) => void | (() => void)

意味: スクロール要素のオフセットを監視する関数。
なぜ必要か: スクロール位置の変更を監視し、それに基づいて表示するアイテムを調整するために使用します。

measureElement

measureElement?: (
el: TItemElement,
  instance: Virtualizer<TScrollElement, TItemElement>
) => number

意味: アイテムのサイズを動的に測定する関数。
なぜ必要か: 各アイテムの実際のサイズを動的に測定する場合に、この関数を使用して正確なサイズを取得できます。

scrollMargin

scrollMargin?: number

意味: スクロールオフセットの起点を指定するオプション。
なぜ必要か: スクロール要素の始まりの位置を調整することで、リストの先頭や末尾に余白を追加できます。特にヘッダーや複数の仮想化リストがある場合に役立ちます。

gap

gap?: number

意味: アイテム間のスペースを指定するオプション。
なぜ必要か: アイテム間のスペースを一貫して維持するために使用します。これにより、アイテムが密集しすぎないようにし、視覚的に分かりやすくします。

lanes

lanes: number

意味: リストを分割するレーンの数(垂直リストの場合は列、水平リストの場合は行)。
なぜ必要か: グリッド状のレイアウトを実現するために、リストを複数の列や行に分割できます。例えば、複数列のリストやギャラリーレイアウトを作成する場合に使用します。

isScrollingResetDelay

	isScrollingResetDelay: number

意味: 最後のスクロールイベント後にスクロール状態をリセットするまでの遅延時間(ミリ秒)。
なぜ必要か: スクロールの終了を正確に検出するために使用します。これにより、スクロールが終了した後に特定のアクションを実行することができます。例えば、スムーズなスクロールアニメーションのために使用します。

*一部割愛しています

VirtualItem

VirtualItemオブジェクトは、Virtualizerによって返される単一のアイテムを表します。このオブジェクトには、アイテムを仮想化されたリストの座標空間内でレンダリングするために必要な情報や、その他の便利なプロパティや関数が含まれています。

export interface VirtualItem {
  key: string | number
  index: number
  start: number
  end: number
  size: number
  lane: number
}
各プロパティとその必要性

key

key: string | number

意味: アイテムの一意のキー。デフォルトではアイテムのインデックスですが、getItemKeyオプションを使用して設定できます。
なぜ必要か: 一意のキーは、Reactがアイテムを効率的に管理するために必要です。キーがあることで、Reactはアイテムの追加、削除、更新を正確に把握できます。

index

index: number

意味: アイテムのインデックス。
なぜ必要か: インデックスは、リスト内のアイテムの順序を特定するために使用されます。これにより、正しい位置にアイテムを配置できます。

start

start: number

意味: アイテムの開始ピクセルオフセット。通常、CSSプロパティ(top/leftやtranslateX/translateY)に対応します。
なぜ必要か: アイテムの正確な位置を設定するために必要です。これにより、アイテムが仮想化されたリスト内で適切な位置に配置されます。

end

end: number

意味: アイテムの終了ピクセルオフセット。この値はほとんどのレイアウトでは必要ありませんが、便利な場合もあります。
なぜ必要か: レイアウトやスクロール計算において、アイテムの終了位置を知ることで、表示範囲の計算を最適化できます。

size

size: number

意味: アイテムのサイズ。通常、CSSプロパティ(width/height)に対応します。アイテムが測定される前は、estimateSizeオプションから返された推定サイズです。測定後は、measureElementオプションによって返されたサイズになります。
なぜ必要か: 正確なサイズ情報は、アイテムのレイアウトと表示に不可欠です。推定サイズと実際のサイズを比較することで、スクロール位置や表示の最適化が行えます。

lane

lane: number

意味: アイテムのレーンインデックス。通常のリストでは常に0に設定されますが、メイソンリーレイアウトでは役立ちます。
なぜ必要か: 複数列やメイソンリーレイアウトで、アイテムを異なる列に配置するために使用します。これにより、複雑なレイアウトでも効率的にアイテムを配置できます。

https://tanstack.com/

Discussion