SWRとは?
2021/12/12
SWRは、NextJS
を作っているVercel社が開発したReact Hooksライブラリであり、データ取得、管理をstale-while-revalidate
という方針に従って行ってくれるライブラリになります。
stale-while-revalidate
は、HTTP RFC 5861で提唱された HTTP キャッシュ無効化戦略です。
stale-while-revalidate
に従ってデータ取得を行う場合、既存のキャッシュを返して、その後にバックグラウンドでデータ取得を行います。
得られたデータと既存のキャッシュに違いがないかを検証し、違いがあった場合はすぐにキャッシュに反映するので、可能な限りキャッシュは最新の状態に保たれるような仕組みになっています。
より詳細を知りたい場合は公式サイトがよくまとめられているので、そちらをご参考ください。
使用方法
簡単に使用方法を紹介します。
必要なライブラリはswr
だけです。
yarn add swr
swrは取得してきたデータをいい感じにキャッシュ管理するライブラリなので、データ取得部分(fetcher)は各自で用意します。
const fetcher = (...args) => fetch(...args).then(res => res.json())
※ 上記ではネイティブのfetch
をラップしたfetcher
関数を作成していますが、fetcher
にはaxios
やGrahQL API
も使えます。
あとはuseSWR をインポートして、任意の関数コンポーネント内で使用するだけです。
import useSWR from 'swr'
function Profile () {
const { data, error } = useSWR('/api/user/123', fetcher)
if (error) return <div>failed to load</div>
if (!data) return <div>loading...</div>
// データをレンダリングする
return <div>hello {data.name}!</div>
}
SWRを使うメリット
SWRを使うと何が嬉しいのか、個人的に良いと思った機能をまとめてみました。
シンプルなキャッシュ機能
Reduxとかだと、取得してきたデータを明示的にstateに突っ込まなくてはいけませんが、useSWR
ではデータを取得すると勝手にいい感じでキャッシュ管理してくれます。
どちらが良いとかはありませんが、Redux抜きでシンプルにフロントのstateが管理できるのはswr
のメリットの一つだと思います。
swr
はurlに応じてデータをキャッシュしているのでどのコンポーネントからでもuseSWR
で同じurlを指定すれば、簡単にキャッシュの取得が可能です。
例えば、以下のようなcustom hooks
を用意して、
function useUsers() {
const { data, error } = useSWR(`/api/users`, fetcher)
return {
users: data,
isLoading: !error && !data,
isError: error
}
}
別々のコンポーネントからそれぞれuseUsers
を使えば、同じusers
のキャッシュを取得できます。
// ページコンポーネント
const Page: React.FC = () => {
return <div>
<Navbar />
<Content />
</div>
}
// 子コンポーネント
const Navbar: React.FC = () => {
return <div>
...
<Avatar />
</div>
}
const Content: React.FC = () => {
const { users, isLoading } = useUsers()
if (isLoading) return <Spinner />
return <h1>Welcome back, {user.name}</h1>
}
const Avatar: React.FC = () => {
const { users, isLoading } = useUsers()
if (isLoading) return <Spinner />
return <img src={user.avatar} alt={user.name} />
}
リクエストの重複排除
上記の例だとusers
取得のリクエストが複数回行われそうですが、useSWR
を使っている場合はAPIへのリクエストは1回になります。
APIとの無駄な通信を気にする必要なく、useSWR
を使ってキャッシュへ手軽にアクセスできるのは地味に便利です。
自動再検証機能(ポーリングなど)
フォーカス時の再検証 公式にある自動再検証の動画がわかりやすいので引用させていただきます。
↓はフォーカス時の再検証を使用して、ページ間でのログイン状態を自動的に同期している様子です。
SWRはブラウザのwindowやtabフォーカス時に、裏側でapiと通信をしキャッシュの更新を自動で行います。 したがって、フォーカスした時点でのフロントは常に最新の状態に保たれます。
ポーリング 掲示板などのリアルタイム更新が必要なアプリでは、WebSocketやポーリングなどの技術が必要になってきます。
WebSocketはサーバー側の実装も大変なので、簡単にリアルタイム更新を実装しようと思うとhttp通信だけで済むポーリングを使用すると思います。
SWRではそのポーリング(定期的な自動再検証)を難しい設定なしで簡単に利用できます。
方法は以下のようにuseSWR
を使用する際に、refreshInterval
のoptionを指定するだけです。
useSWR('/api/todos', fetcher, { refreshInterval: 1000 })
この場合は1000ms
間隔でポーリングが行われます。
↓動画のようなイメージです。
機能としてはシンプルなポーリングですが、画面には表示されていないコンポーネントが裏側で常に通信を行ってしまう可能性に注意しなければなりません。
今までのポーリングはそこらへんを気をつけながら実装するのが面倒だったのですが、SWR
ではフックに関連付けられているコンポーネントが画面に表示されている場合にのみ通信を行う仕様になっています。
とにかく自動再検証を行う上で無駄なリクエストはできるだけ行わないという意思を感じます 😊
自動再検証の無効化
ここまで紹介した自動再検証機能はなくてもいい!っていう場合もあると思うので、その場合はuseSWR
の代わりにuseSWRImmutable
を使えばOKです 👌
import useSWRImmutable from 'swr/immutable'
// ...
useSWRImmutable(key, fetcher, options)
適切なエラーハンドリング
個人的に良いと思ったその他の機能としては、リクエストエラー時に適切なアルゴリズムで再試行をしてくれる点が挙げられます。
具体的にはエラー時に、SWR
はExponential backoffというアルゴリズムに従って、リクエストを自動再試行してくれます。
これにより、アプリはエラーから素早く回復することが可能となるだけではなく、再試行のしすぎでリソースを無駄にすることはありません。
このようなエラーハンドリングを自前で実装しようと思うと結構面倒くさいので、最初からライブラリに含まれているのはかなり良いと思いました 🤩
もちろん、Exponential backoffアルゴリズムに則ったエラーハンドリングを以下のようにオーバーライドすることも可能です。
useSWR('/api/user', fetcher, {
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
// 404では再試行しない。
if (error.status === 404) return
// 特定のキーでは再試行しない。
if (key === '/api/user') return
// 再試行は10回までしかできません。
if (retryCount >= 10) return
// 5秒後に再試行します。
setTimeout(() => revalidate({ retryCount }), 5000)
}
})
※上記の例では各エンドポイントごとにエラーハンドリングを設定していますが、グローバルにエラーハンドリングの設定を行うことも可能です。
その他のメリット
個人的に良いと感じたその他のメリットとしては以下が挙げられます。
- ライブラリが軽量
- typescript対応
ここで紹介した機能以外にも細かい機能がたくさんあるので、詳しくは公式サイトをご参考ください。
おわり
シンプルだけど、かなり実用的 🤩