Tanstack Virtual とは?
Tanstack Virtual は、大きなスクロール要素を効率的にレンダリングするための仮想化ライブラリです。
どれだけ多くの要素があっても、実際の DOM では一定数だけをレンダリングする技術です。DOM にレンダリングされる要素が多くなるほど、ある時点からパフォーマンスが急激に低下するため、表示すべき要素が多い場合にはこのような仮想化技術を適用することができます。
仮想化ライブラリは Tanstack の他にも react-virtualized、react-window などがあります。
問題
従来は上記のようにグリッドで構成されており、ビューポートによって表示されるカードの数が変わるようにしていました。
Tanstack Virtual の例を見ればわかりますが、Tanstack Virtual を使用すると、スクロール div 内のアイテムを absolute で適用し、transform を通じて位置を調整するようになっています。
つまり、既存の方法で親に Virtualizer
を適用するだけでは解決できない問題でした。Tanstack Virtual を使用する場合、1 行または 1 列に 1 つの要素だけが入るようにする必要があります。
解決策
既存のリストアイテムは 1 次元配列になっており、これを単純に map を回してレンダリングしていました。
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 つの要素だけが入るようにするため、以下のようにコードを変更しました。
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 次元配列にしました。
図で見るとこんな感じです。
注意すべきは、1 行に 1 つの要素だけが入るようにすること、そしてその 1 つの要素の中に何個のアイテムを入れるかです!
実際のコードは以下の通りです。
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 にレンダリングされるように仮想化することができました!