Open16
Tanstack Virtualでスクロール時に長時間のブロッキングが発生する症状について
ピン留めされたアイテム
前提条件
CPUの実行速度が遅い端末などで、スクロール時の再レンダリングが非常に遅い現象があった。
確認環境としては ChromeのDevtoolsでCPUスロットリング x20 で実行した際に再描画に1s以上かかってしまうという感じ。
ここでreact-dom
のflushSync
が呼び出されているので結構な計算コストになりそう。
ワークアラウンド
const virtualizer = useVirtualizer({
count: 200,
getScrollElement: () => ref.current,
estimateSize: () => 120,
overscan: 7,
// ここのコールバックの第二引数に false を渡すとOK
observeElementOffset: (_, cb) => cb(0, false),
});
これだと、スクロール時に更新が行われなくなってしまったので直接パッチを適用して検証してみる。
@tanstack/react-virtual/dist/esm/index.js
...options,
onChange: (instance2, sync) => {
var _a;
- if (sync) {
- flushSync(rerender);
- } else {
+ // if (sync) {
+ // flushSync(rerender);
+ // } else {
rerender();
- }
+ // }
(_a = options.onChange) == null ? void 0 : _a.call(options, instance2, sync);
}
};
公式ドキュメント
onChange
のsync
はどこから渡されるのか
ここの引数に入ってくるsync
のでどころ調査。
ここのオプションは下記で@tanstack/virtual-core
からインポートされているVirtualizer
インスタンス化するときに渡されているっぽいので@tanstack/virtual-core
に移動する。
ここらへんがVirtualizer
Class
Virtualizer.notify()
というプライベートメソッドによってonChange
に指定したコールバックが実行されるっぽい。
ただ、ここでもsync
は引数なので更に定義を調べる。
更にVirtualizer.notify()
はVirtualizer.maybeNotify()
から呼ばれている。
ここのVirtualizer.maybeNotify()
ではmemo
という関数が呼ばれているがこれはreact
のメモ化ではなく独自に定義したメモ化関数っぽい。
memo()
の定義はここ。
memo()
は第一引数に依存配列を取得する関数を定義すると、第二引数のコールバックの引数に依存配列が渡されるようになる。
memo(
() => ['foo', 2],
(foo, num) => console.log(foo, num);
// => 'foo' 2
);
この前提を持って、改めてmaybeNotify()
を見てみる。
そうすると、ここで渡されているのはthis.isScrolling
であることが分かる。