React Hooks での Data Fetching について(useSWR)
React Hooks での Data Fetching は、SWRを使うと良いらしいので、調査
SWR は Next.js などを提供する Vercel が開発している
SWR という名前は、stale-while-revalidate
の頭文字を取っている
stale-while-revalidate について
HTTP ヘッダーのCache-Control
にstale-while-revalidate=<seconds>
を指定できる
似たディレクティブにmax-age
がある
max-age
とstale-while-revalidate
の挙動について
max-age=<seconds>
を指定した場合
Cache-Control: max-age=1000s
キャッシュが更新されてから、1000s はキャッシュを返す
1000s 過ぎていたら Fetching が走る
stale-while-revalidate=<seconds>
を指定した場合
Cache-Control: stale-while-revalidate=1000
キャッシュが更新されてから、1000s はキャッシュを返す
同時にバックグラウンドで Fetching が走り、キャッシュを更新する
1000s 過ぎていたら Fetching が走る
stale-while-revalidate
単体で指定するような使い方は調べた限りだと見かけなかった
両方指定した場合
Cache-Control: max-age=1000s, stale-while-revalidate=500
キャッシュが更新されてから、1000s はキャッシュを返す
max-age
経過後、そこから 500s はキャッシュを返すが、バックグラウンドで Fetching が走り、キャッシュを更新する
もし、この Fetching が 500s を超えると、キャッシュが破棄され、次回のリクエスト時に Fetching が走る
1500s 過ぎていたら Fetching が走る
基本的にはstale-while-revalidate
の値だけを考慮すれば良さそうだが、これが利用できないブラウザをサポートする場合は、max-age
の値も考える必要がありそう
stale-if-error=<seconds>
Cache-Control: stale-if-error=1000s
Fetching でエラーになった場合、キャッシュを返す
キャッシュが更新されてから、1000s 過ぎていたら Fetching でエラーになっても何もしない
他の Expire ディレクティブと組み合わせることもできる
Cache-Control: max-age=1000s, stale-while-revalidate=500, stale-if-error=2000s
ブラウザのサポート
2021 年 5 月時点では、Chrome,Edge,Firefox がstale-while-revalidate
をサポートしている
stale-while-revalidate
をサポートしていないブラウザは暗黙的にこれを無視する
(max-age
が指定されていれば、これを使用する)
stale-if-error
はどのブラウザもサポートしていない(CDN 等でサポートされている)
参考
キャッシュ備忘録
キャッシュのキー
キャッシュのキーは URL
bundle.js
などキャッシュされたくないコンテンツは、bundle-[hash].js
のように CI 等のビルド時にハッシュ値を付与して対処する
Authorization ヘッダーについて
Authorization ヘッダーを持つリクエストはデフォルトではキャッシュされないが、public
,max-age
,s-maxage
のいずれかが Cache-Control に指定されていれば、キャッシュされる
Vue 版の SWR
Vue 版の SWR であるSWRVっていうのがあるっぽい
ドキュメントにIt is largely a port of swr.
(SWRV の大部分は SWR の移植です)って書いてある
普段は Vue を書くことが多いので、今度使ってみよう
SWR について
特徴
- React Hooks での利用が前提
- useSWR というカスタムフックが提供される
- 非同期処理が必要なデータ取得を簡潔に実装できる
- 取得したデータの状態管理もできる
-
stale-while-revalidate
と同じようなことがメモリ上でできる - TypeScript,ReactNative,GraphQL をサポート
概要
以下は公式の例
import useSWR from 'swr'
const fetcher = url => fetch(url)
function Profile() {
const { data, error } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
useSWR は以下のように使う
const { data, error } = useSWR('/api/user', fetcher)
-
data
がundefined
- ローディング状態
-
data
がPromise.resolve()
された値- データ取得完了
-
error
がPromise.reject()
された値- データ取得エラー
useSWR の第一引数がキャッシュのキーとなる
第二引数は Promise を返す関数となる
useSWR は取得したデータをメモリ上にキャッシュする
stale-while-revalidate
のように、データ取得の際にキャッシュを返すが、同時にデータ取得も行い、キャッシュを更新する
メモリ上にキャッシュするので初期表示時はデータ取得が必ず走るし、メモリが解放されたらキャッシュは消える
キャッシュの永続化は、将来的にはサポートされるかもしれない
参考
fetcher について
useSWR の第二引数に渡すfetcher
については Promise を返す必要がある
Promise を返せれば、何でも良い
以下の公式サイトの実装例でも developit/unfetch(Fetch API の polyfill)、axios、GraphQL が紹介されている
また、useSWR で Fetch API かつ GET を利用するのであれば、fetcher
を省略できるらしい
const { data, error } = useSWR('/api/user')
しかし、現状は利用する HTTP クライアントは統一したいので、axios にしようと考えている
- POST などの更新系メソッド自体は、useSWR を使わず axios のみで実装しようと思っている
- 更新系メソッドは、状態管理を想定していないため
- Fetch API でこの辺書きたくないため
- useSWR を使わないという選択をした時に axios の方がうまく分離できそう
mutate について
useSWR はキャッシュベースでデータの状態管理を行うので、このキャッシュを手動で新しくしたいケースが度々出てくる
mutate を使うことで実現できる
以下のドキュメントを見ると、何パターンかの更新手段がありそう
ただし、大きく分けると 2 パターン
データを再取得する
- 実装が単純
- バグを生みにくい
- データを再取得するコストがかかる
import useSWR, { mutate } from 'swr'
mutate('/api/user')
データを再取得せずに手動で更新する
- 実装が再取得するパターンよりは複雑になる
- useSWR による更新と手動更新が混在するので、双方の整合性が合ってないとバグを生む
- データを再取得するコストがかからない
import useSWR, { mutate } from 'swr'
mutate('/api/user', { ...data, name: newName }, false)
もしくは、useSWR から mutate が取得できる
こちらの方が取得処理と更新処理のスコープが同じ場所になるので良さそう
const { data, mutate } = useSWR('/api/user', fetcher)
mutate({ ...data, name: newName })
React Query
SWR について調べていると、React Query があることを知った
概要だけ見たけど、SWR とほぼ一緒だった
npm trends を見ると、React Query の方が上だった
(グラフの形が同じすぎる)
今回は Next.js を使っており、それと同じ Vercel 製ということで SWR を使おうかなと思う