useSWRのkeyには配列を渡すと良い, ただし配列に渡すオブジェクトには気をつけよう
useSWRはVercel製のReact向けクエリライブラリです. いわゆるfetch処理の状態管理やキャッシュに利用できるライブラリです.
本題とは関係ないですが, 最近公式ドキュメントがNextraっぽくなりました. というか同タイミングでVercel傘下のOSSが公式ドキュメントリニューアルした気がします.
useSWRのkeyはそのまま値がfetcherの第一引数に渡るという仕様があります.
以下の3つの関数が等価になるわけです.
useSWR('/api/user', () => fetcher('/api/user'))
useSWR('/api/user', url => fetcher(url))
useSWR('/api/user', fetcher)
そして, このkeyはキャッシュのKVにおけるkeyになるわけです.
そしてこのkeyには配列が渡せます.
詳しくは「複数の引数」の項目で記述されていますが, token等の付属情報やメタデータをurlとは別に渡すために(tokenってキャッシュに含めていいんですかね?まあrevokeされるからいいのか)
const { data: user } = useSWR(['/api/user', token], ([url, token]) => fetchWithToken(url, token))
かなり便利です. 特にfirestoreなどのfetch処理を使わなかったり, fetch部分の処理が抽象化されていてパラメーターを別で渡す場合でも利用可能です.
useSWR([id, conditions], fetcher)
みたいなことができるわけです. これの利点は以下の2点です.
- fetch処理を分離できる.
- fetch処理をuseSWRのfetcherに合わせて書きすぎると後から癒着して外しづらくなります. fetch処理単体はサービス全体のどこからでも呼び出せるように分離しておくべきです
- key生成部分を関数化することでグローバルなkey管理を同時にできる.
- 以下のように
getHogeKeyを作り, その値をas constでリテラル的に扱うことで型安全にkey管理ができます.(そして衝突を避けられる)const getHogeKey = (param1: number, param2: string) => ["/base-url", param1, param2] as const
- 以下のように
ただ便利なように見えて, 不利な点もあります. useSWRはkeyに配列を渡した時その結果をJSON.stringify でシリアライズされた値の配列をjoinで結合するような処理をしています. (復元は逆順)
つまり, firestoreのインスタンスやclassインスタンス, もしくは複雑性の高いオブジェクトを含めるとそれらが全てkeyになります. このkeyはブラウザ上でメモリ上にどんと乗っかるので数十MBあるオブジェクトなら非圧縮でそれがメモリを圧迫するわけです.
1つや2つのkeyならいいですが, アプリケーション全体で複数扱うようなkeyの場合それだけで数百MB占有します. そしてGCされません. やめましょう.
ということでuseSWRのkeyは配列渡すといいもののプリミティブな値やできるだけ小さなオブジェクトに絞った方がいいよということでした.
Discussion