🌊

SWRのあなたが知らないNつのこと

2022/11/05に公開

https://swr.vercel.app/ja

(私の中で)神のライブラリと名高いSWRですが、見かけのシンプルさによらず意外と多くの機能があります。

今回はドキュメントに記載されていることで、ドキュメントをちゃんと読んでないユーザーが知らないがちのことについて書きます。
大体下に行くほどマニアックな知識になります。

fetchを抑制する(conditional fetch)

https://swr.vercel.app/ja/docs/conditional-fetching

条件付きフェッチに書いてあることそのままですが、swrはkeyにnullな値を渡すことでfetchを抑制できます。これを使ってログインしてない時はfetchを抑制するなどの書き方や、あるfetchが終わってから初めて次のfetchを実行するなどの書き方ができます。

const { data: user } = useSWR('/api/user')
// userのfetchが終わるまで以下のfetchは抑制される
const { data: projects } = useSWR(user ? `/api/projects?uid=${user.id}` : null)

N秒ごとにfetchする(refresh inteval)

https://swr.vercel.app/ja/docs/revalidation#定期的な再検証

useSWRのrefreshIntervalオプションにより、fetchを定期的に走らせることができます。

useSWR('/api/todos', fetcher, { refreshInterval: 1000 })

エラー時はデフォルトでexponential backoffでリトライがかかる

https://swr.vercel.app/ja/docs/error-handling#エラー時の再試行

デフォルトの挙動で、エラーが返ってきた時はexponential backoffを使ったリトライが行われます。リトライを勝手にしてくれるのは便利ですが、個人的には400系エラーでリトライする意味はないと思うので、そのあたりも加味したリトライコードを自前で書いてしまった方がよいと思っています。

https://github.com/vercel/swr/blob/e81d22f4121743c75b6b0998cc0bbdbe659889c1/_internal/utils/config.ts#L16-L38

ちなみに現状の実装は上記(本当にリトライしてるだけ)

useSWRの引数はメモ化する必要はない

https://swr.vercel.app/ja/docs/arguments#オブジェクトの受け渡し

useSWRの引数はシリアライズされるのでメモ化の必要はないです。考えてみれば確かにuseSWRには配列などを雑に渡してしまう場面も多く、浅い比較で再レンダリングが発火してしまうよりはシリアライズしてくれる方が便利そうです。

https://github.com/vercel/swr/blob/85c84ceb578cb3d28362659bd9b82a03fd39be6b/_internal/utils/serialize.ts

serializeの関数は上記で定義されています。
serializeと言っても丸ごと全体が文字列などになるわけではなく、hashを計算してそれをkeyとするようです。hashの計算は以下。

https://github.com/vercel/swr/blob/85c84ceb578cb3d28362659bd9b82a03fd39be6b/_internal/utils/hash.ts

serializeの実装をよく見ると、配列であっても空の場合は単に空文字列にfallbackされるなどの挙動があり、直観的に再レンダリングをしてほしいケースで正しく再レンダリングが行われ、そうでないケースで再レンダリングが抑制されるような設計になっているのが見て取れます。

fetchが成功した時に特定の処理を実行できる(onSuccessオプション)

https://swr.vercel.app/ja/docs/options#オプション

useSWRのonSuccessオプションで、fetchが完了した時に〇〇するみたいな処理が書けます。useEffect使うよりこっち使った方がいいですね。
(と言いつつオプション渡すのが面倒でuseEffectで書いたりしがちだけど…)

重複したfetchは抑制される

https://swr.vercel.app/ja/docs/advanced/performance#重複排除

useSWRを含むコンポーネントをあちこちに配置した場合、同じキーに対してfetchが何度も呼ばれると非効率です。swrはこのようなケースでも重複排除を行ってくれます。
単にキャッシュが効くだけでなく、重複も排除してくれるのでますます雑にuseSWRが呼べて大変便利です。

fetchした結果に変化がない場合、再レンダリングはトリガーされない

https://swr.vercel.app/ja/docs/advanced/performance#詳細な比較

(上記の説明は厳密には正確ではありませんがまあ言いたいことは伝わると思うので許して)

fetchの結果は詳細な比較が行われ、同じであれば再レンダリングがトリガーされないので雑にrefetchしてもコンポーネントが意味もなく再レンダリングされることはありません。

https://github.com/vercel/swr/blob/e81d22f4121743c75b6b0998cc0bbdbe659889c1/_internal/utils/config.ts#L40-L41

また、詳細な比較とはデフォルトの挙動ではハッシュ値を用いているようです。
自前でcompareを実装する必要がある時、戻り値のサイズに対するcompareのコストには注意したいところです。(compareを自前で実装したくなったことないけど…)

まとめ

swrはパフォーマンスを気にしたベストプラクティス的なAPI設計がかなり光っており、シンプルなAPIの割にこの辺りがしっかりしているので、自前で適当にuseEffectとfetchなどで実装するよりも明確にメリットがあると言えると思います。

また、swrの「useSWRをあらゆるところで雑に呼び、必要に応じてきちんとキャッシュする」みたいな思想がドキュメントから透けて見えるところは個人的に結構好きです。私も改めてドキュメントを読み返し、swrを使うからにはswrの思想に則ったコードを書いていきたいと思いました。

余談ですが、Suspenseの流れなのか分かりませんがReact公式がこれからこの辺りのキャッシュ関連のAPIをサポートしていきそうな感じがあるのでswrも今後さらに薄いライブラリになっていくのかもしれません。
個人的にはキャッシュはReact側で持って、swrはあくまで(再レンダリングを適切に抑制するための)fetchのアダプターとして振舞ってくれるなら棲み分けもできていていい感じになりそうと思っています。まああくまで将来的な話なのでどうなるのかは不明ですが。

Discussion