Tanstack Virtual の適用

profile image

Tanstack Virtual でリスト仮想化を行い、DOM を最適化しよう!

この記事は DeepL によって翻訳されました。誤訳があれば教えてください!

Tanstack Virtual とは?

Tanstack Virtual は、大きなスクロール要素を効率的にレンダリングするための仮想化ライブラリです。

tanstack-virtual-example1

どれだけ多くの要素があっても、実際の DOM では一定数だけをレンダリングする技術です。DOM にレンダリングされる要素が多くなるほど、ある時点からパフォーマンスが急激に低下するため、表示すべき要素が多い場合にはこのような仮想化技術を適用することができます。

仮想化ライブラリは Tanstack の他にも react-virtualized、react-window などがあります。

問題

Image.png

従来は上記のようにグリッドで構成されており、ビューポートによって表示されるカードの数が変わるようにしていました。

Tanstack Virtual の例を見ればわかりますが、Tanstack Virtual を使用すると、スクロール div 内のアイテムを absolute で適用し、transform を通じて位置を調整するようになっています。

つまり、既存の方法で親に Virtualizer を適用するだけでは解決できない問題でした。Tanstack Virtual を使用する場合、1 行または 1 列に 1 つの要素だけが入るようにする必要があります。

解決策

既存のリストアイテムは 1 次元配列になっており、これを単純に map を回してレンダリングしていました。

typescript
const itemList = [item1, item2, item3, item4, item5, item6, item7];

return <div className='grid grid-cols-1 tablet:grid-cols-3 desktop: grid-cols-5'>
  itemList.map(item => <Card item={item} />)
</div>

上で説明したように、1 行に 1 つの要素だけが入るようにするため、以下のようにコードを変更しました。

typescript
let columnSize = 3; // 1, 3, 5

const chunkedItemList = chunkArray(itemList, columnSize);
// [[item1, item2, item3], [item4, item5, item6], [item7]]

return <div>
  chunkedItemList.map(list => <div className='grid grid-cols-1 tablet:grid-cols-3 desktop: grid-cols-5'>
    {list.map(item => <Card item={item} />)}
    </div>)
</div>

既存のアイテムリストを 1 次元配列から columnSize 分だけ分割して 2 次元配列にしました。

図で見るとこんな感じです。

Image.png

注意すべきは、1 行に 1 つの要素だけが入るようにすること、そしてその 1 つの要素の中に何個のアイテムを入れるかです!

実際のコードは以下の通りです。

typescript
const [columnSize, setColumnSize] = useState(COLUMN_SIZE.desktop);

const list = chunkArray(
  flatPages(data) ?? [],
  columnSize
);

const rowVirtualizer = useVirtualizer({
  count: list?.length,
  getScrollElement: () => document.getElementById("main-section"),
  estimateSize: () => 390,
  overscan: 1,
});

useEffect(() => {
  if (isLabtop) setColumnSize(COLUMN_SIZE.labtop);
  else if (isMobile) setColumnSize(COLUMN_SIZE.mobile);
  else setColumnSize(COLUMN_SIZE.desktop);
}, [isLabtop, isMobile]);

{
  rowVirtualizer.getVirtualItems()?.map((virtualRow) => {
    const row = list[virtualRow.index];
    if (!row) return null;
    return (
      <div
        key={virtualRow.key}
        className={`absolute top-0 left-0 w-full`}
        style={{
          transform: `translateY(${virtualRow.start}px)`,
        }}
      >
        <div className="grid grid-cols-1 laptop:grid-cols-3 desktop:grid-cols-5 w-full gap-6 place-items-center items-stretch">
          {row.map((item) => (
            <Card
              key={item.designId}
              item={item}
              isVault={isVault}
            />
          ))}
        </div>
      </div>
    );
  });
}

結果として、以下のように特定の数だけが実際の DOM にレンダリングされるように仮想化することができました!

tanstack-virtual-example2

❤️ 0
🔥 0
😎 0
⭐️ 0
🆒 0