SWRとは、Vercel社(Next.jsやVercelを作っている会社)が作成した
React Hooksで作成されたデータフェッチ用のライブラリです。
SWR何が嬉しいのか
- ライブラリが軽い!(48.9kB)
- Reactにおけるfetch処理のデータがキャッシュにより簡単に再利用できる。(Reduxなしで!)
- エラー時は自動でいい感じに再取得してくれる!
- ポーリング(一定間隔でAPIを叩く)も簡単な設定で可能!
- フォーカス時に勝手に再取得して最新のデータに更新してくれる!
- TypeScriptデフォルトサポート
ものすごーーく便利です。
Get Started
適当にReactの環境を作る
$ npx create-react-app my-app --template typescript
$ cd my-app
SWRをインストール
$ npm i swr -S
import useSWR from 'swr'
import { fetcher } from './fetcher'
export const useHelloSwr = () => {
const {data, error} = useSWR(`/api/hello.json`, fetcher)
return {
data,
isLoading: !error && !data,
isError: error
}
}
export default App;
export const fetcher = (url: string) => fetch(url).then(res => res.json())
useSWRの第一引数にAPI叩き先、第二引数にPromiseを返すfetch処理の関数を置きます。
import React from 'react';
import { useHelloSwr } from './fetch/useHelloSwr'
import './App.css';
function App() {
const { data, isLoading, isError } = useHelloSwr()
if(isLoading) return <div>Loading</div>
if(isError) return <div>Error</div>
return (
<div className="App">
{data.hello}
</div>
);
}
export default App;
戻り値のdata,isLoading,isError
を使って状態をビューに落とし込むだけ。
とてもシンプルで再利用しやすいです。
エラーハンドリングについて
SWRではexponential backoffアルゴリズムを用いて良い感じにリクエストを再試行してくれます。
exponential backoffアルゴリズムとは簡単にいうと、失敗回数が多くなるほどに再実行の間隙が指数関数的に広くなるというもので、無駄な再実行を抑えてくれるものです。
【参考】https://codezine.jp/article/detail/10739
この設定はswrのフックスごとにオプションをオーバーライド(上書き)することができます。
useSWR('/api/hello.json', 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)
}
})
また、オプションshouldRetryOnError: false
を設定すると、この再試行の機能を無効にできます。
const {data, error} = useSWR(`/api/hello.json`, fetcher, {
shouldRetryOnError: false
})
自動再検証について
SWRではデフォルトで自動再検証の機能が有効になっています。
自動再検証とは、ページにフォーカスを合わせた時・タブを切り替えた時に再フェッチをしてくれるというものです。
この機能で程度データを同期できるので、スモールな形で擬似的なリアルタイムを実装できる優れものです。
しかし、データを更新する必要がないもの(データが一定であるものや、ある地点のデータだけが欲しい場合)再フェッチを行うのはもったいないので、その場合は必ずこの機能を無効化するのを忘れないようにしましょう。
繰り返しますが、自動再検証はデフォルトで有効です。
無効にするには、useSWRImmutable
フックを使います。
import useSWRImmutable from 'swr/immutable'
// ...
useSWRImmutable(key, fetcher, options)
条件付きfetch
こちらの実装も非常に簡単です。
特定の条件下だけfetchしたい、という場合においては、
useSWR
第一引数にfalsyな値を渡すようにするか、関数を入れて、特定条件下でfalsyを返すorエラーをthrowするようにしておけばOKです。
// shouldFetchがfalsyだとfetchしない
const { data } = useSWR(shouldFetch ? '/api/data' : null, fetcher)
// 第一引数関数がfalsyを返却するとfetchしない
const { data } = useSWR(() => shouldFetch ? '/api/data' : null, fetcher)
// user.id が定義されてない場合エラーをスローするのでfetchしない
const { data } = useSWR(() => '/api/data?uid=' + user.id, fetcher)
手動で再取得したい場合
自動でfetchを叩く以外でも、特定の条件でデータを再取得したい場合もあるかもしれません。(更新ボタンを押すなど)
そういった場合は、useSWRConfig
フックから発行されるmutate
関数を使用します。
import useSWR, { useSWRConfig } from 'swr'
function App () {
const { mutate } = useSWRConfig()
return (
<div>
<button onClick={() => {
// このキーを使用してすべての SWR に再検証するように指示します
mutate('/api/user')
}}>
Logout
</button>
</div>
)
}
その他
ここで全ては紹介しませんが、その他にも
- Suspence機能に対応させる
- ミドルウェアを噛ませる
- 無限ローディングを実現する
など様々な機能があり、大体のユースケースをカバーできているように思えました。
実際実装でつまづいたポイント
Q.一部が動的に変更されるidがあるようなAPIはどうやって実現するか?
ポイントとして、データの識別子は、useSWRの第一引数で行われているということです。
つまり、ここが変わらなければキャッシュが効いてしまい、思うような動的な変更ができません。
// tokenが変わってもキャッシュの効いた前のデータを返してしまうケース
useSWR('/api/user', url => fetchWithToken(url, token))
キャッシュと関連づけるために、第一引数を配列で渡してあげればOKです。
useSWR(['/api/user', token], fetchWithToken)