各セルが編集可能なそこそこ大きめのテーブルをreact-virtuosoで扱う
2023-11-19

各セルが編集可能なテーブル(つまり tr > td > input ということ)を扱いたい時を考える

普通に実装すると、ラッパーコンポーネントのuseStateでデータと更新関数を初期化してセルコンポーネントまでバケツリレーし、
セルコンポーネントのonChangeでデータ更新…という感じになると思う
データ量がそれほどでも無いときはこれで全く問題ないが、データが増えてくるとブラウザが重くなりしんどみが発生する

具体的には10万セルで初期表示とセル編集に各5秒程度ラグる
https://ngmtine.github.io/table_virtuoso_example/
↑の「プレーンなreactでのテーブル実装例(仮想スクロールライブラリ不使用)」が愚直な実装例
セルクリックしたり(クリックするとピンク色になります)、「編集済みのみ表示」ボタン押すときの挙動がアホほど重いのがわかると思います

(本筋とは逸れるけどgithub pagesでurl遷移を扱うのわかんなくて匙投げた、
url直打ちしても404になるので手でページ移動してください)

この実装でブラウザが重くなる理由は当然で、セル編集が発生するたびにテーブル全体のデータが更新されご丁寧に全セルが再レンダリングされるため

ではセルごとに独立して状態を持ってメモ化すればと考えても初期レンダリングに時間がかかるのは変わらないし、そもそもDOMというのは重いため行数がある程度になってくるとどうしてもブラウザが硬直してしまう
以前別件でデカめのデータ扱った時、バニラjsですら10数万行程度で指定端末のブラウザが死ぬということがあった(そもそもページネーション使えという話だが…)

というわけで仮想スクロールライブラリの使用を検討する
なんかいろいろあるっぽいけどざっと見てみた感じ react-virtuoso がシンプルで良さげだったので実装してみた
使用感は先程のurlの「rect-virtuoso 実装例」の方です

f12開いて分かる通り、スクロールするたびに画面内に表示するだけのtrがレンダリングされる
このため初期表示もセル編集時もシャキシャキで非常に体験が良い
実装はこちら
スタイリングがカスなのは本筋とは関係ないからです

注意点として、仮想スクロールはスクロールのたびに要素が削除&作成されるため、セルが独立して状態を持てない(const [hoge] = useState()とかしてもスクロールで初期化される)

TableVirtuosoに渡すitemContentとfixedHeaderContentは呼び出し元コンポーネント内で定義すればクロージャで呼び出し元コンポーネントの変数にアクセスできる
当初セルコンポーネント内で元データ使用するためにハンドラ関数のカスタムフック作ったりjotaiとかのグローバル状態管理ライブラリ使ったりしてたのでこれ気づいた時アハ体験だった、もしかしてreactあるあるイディオムだったりするのかしら

あといくら仮想スクロールといえど、例えばタブレットで勢いよくスワイプしたりロジマウスとかのベアリングホイールでシュイーンってしたりするときも律儀に要素の削除と作成が実行されるので微妙にもたつきを感じるかもしれない
これはどうしようもない

というわけで今回の実装はセルのonChangeでデータ更新があったときにどうしても全セル再レンダリングされるのを仮想スクロールで緩和しましょうというお話でした
ベストは仮想スクロールかつデータ更新時に単一セルのみ再レンダリングされるような実装にすることだけどそんな方法あんのか?セルのメモ化だけでできるのかしら よく分からなかったので今後の宿題としましょう

ともあれテーブルサクサクでよかったですね おわり