Closed8

React Hooks での Data Fetching について(useSWR)

kurosamekurosame

stale-while-revalidate について

HTTP ヘッダーのCache-Controlstale-while-revalidate=<seconds>を指定できる
似たディレクティブにmax-ageがある

max-agestale-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

ブラウザのサポート

https://caniuse.com/?search=stale-while-revalidate

2021 年 5 月時点では、Chrome,Edge,Firefox がstale-while-revalidateをサポートしている

stale-while-revalidateをサポートしていないブラウザは暗黙的にこれを無視する
max-ageが指定されていれば、これを使用する)

stale-if-errorはどのブラウザもサポートしていない(CDN 等でサポートされている)
https://caniuse.com/?search=stale-if-error

参考

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Cache-Control
https://blog.jxck.io/entries/2016-04-16/stale-while-revalidate.html

kurosamekurosame

キャッシュ備忘録

キャッシュのキー

キャッシュのキーは URL
bundle.jsなどキャッシュされたくないコンテンツは、bundle-[hash].jsのように CI 等のビルド時にハッシュ値を付与して対処する

Authorization ヘッダーについて

Authorization ヘッダーを持つリクエストはデフォルトではキャッシュされないが、public,max-age,s-maxageのいずれかが Cache-Control に指定されていれば、キャッシュされる

kurosamekurosame

Vue 版の SWR

Vue 版の SWR であるSWRVっていうのがあるっぽい
ドキュメントにIt is largely a port of swr.(SWRV の大部分は SWR の移植です)って書いてある

普段は Vue を書くことが多いので、今度使ってみよう

kurosamekurosame

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)
  • dataundefined
    • ローディング状態
  • dataPromise.resolve()された値
    • データ取得完了
  • errorPromise.reject()された値
    • データ取得エラー

useSWR の第一引数がキャッシュのキーとなる
第二引数は Promise を返す関数となる

useSWR は取得したデータをメモリ上にキャッシュする
stale-while-revalidateのように、データ取得の際にキャッシュを返すが、同時にデータ取得も行い、キャッシュを更新する

メモリ上にキャッシュするので初期表示時はデータ取得が必ず走るし、メモリが解放されたらキャッシュは消える
キャッシュの永続化は、将来的にはサポートされるかもしれない
https://github.com/vercel/swr/issues/16

参考

https://swr.vercel.app

kurosamekurosame

fetcher について

useSWR の第二引数に渡すfetcherについては Promise を返す必要がある
Promise を返せれば、何でも良い
以下の公式サイトの実装例でも developit/unfetch(Fetch API の polyfill)、axios、GraphQL が紹介されている
https://swr.vercel.app/docs/data-fetching

また、useSWR で Fetch API かつ GET を利用するのであれば、fetcherを省略できるらしい

const { data, error } = useSWR('/api/user')

しかし、現状は利用する HTTP クライアントは統一したいので、axios にしようと考えている

  • POST などの更新系メソッド自体は、useSWR を使わず axios のみで実装しようと思っている
    • 更新系メソッドは、状態管理を想定していないため
    • Fetch API でこの辺書きたくないため
  • useSWR を使わないという選択をした時に axios の方がうまく分離できそう
kurosamekurosame

mutate について

useSWR はキャッシュベースでデータの状態管理を行うので、このキャッシュを手動で新しくしたいケースが度々出てくる
mutate を使うことで実現できる

以下のドキュメントを見ると、何パターンかの更新手段がありそう
https://swr.vercel.app/docs/mutation

ただし、大きく分けると 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 })
このスクラップは2021/05/24にクローズされました