SWRを使ってみる
それでは、実際の使い方を見ていきましょう。実物のAPIを使ったほうがわかりやすいので、サンプル内ではソーシャルニュースサイトHacker NewsのAPIであるHacker News APIを利用します。
まずはcreate-react-appで作成した環境に、SWRをインストールします(リスト3)。
$ npm install swr
これだけで準備は完了です。それではまず、単純なデータを返すAPIにアクセスしてみましょう。Hacker News APIの/topstories.jsonエンドポイントは、人気の記事一覧をnumber型の記事IDの配列で返します。ひとまずこれの上位10件を並べてみましょう(リスト4)。
import useSWR from 'swr';
const fetcher = (...args) => fetch(...args).then(res => res.json()); // (3)
function App() {
const { data, error } = useSWR(
'https://hacker-news.firebaseio.com/v0/topstories.json', // (1)
fetcher // (2)
);
if (error) {
console.error(error);
return <div>エラーが発生しました。コンソールを確認してください。</div>
}
if (!data) {
return <div>読み込み中...</div>
}
// dataにはnumber型の配列が返される
const top10items = data.slice(0, 10); // (4)
return (
<div>
<h1>新着記事のID一覧</h1>
<ul>
{top10items.map((itemId) => (
<li>{itemId}</li>
))}
</ul>
</div>
);
}
export default App;
useSWRの第一引数には、(1)のようにアクセス先のURLを指定します。キャッシュのキーを兼ねており、同じURLに複数回アクセスするとキャッシュが効く設計になっています。(2)の第二引数はfetcherと呼ばれており、実際にどの通信ライブラリを利用して通信を実施するかを設定できます。
本サンプルでは(3)のように標準のwindow.fetch関数を利用しました。公式ドキュメントではAxiosのようなサードパーティの通信ライブラリを利用する方式も紹介されています。APIからは数百件(執筆時は482件)のIDが返却されるので、(4)で先頭10件のみに減らしています。
リスト4のコードを実行すると、図2の通りになります。
IDの一覧が表示されましたね。次は、記事のIDをクリックしたときにタイトルとURLが現れるようにしてみましょう。記事のIDをpropsで受け取って、データの取得と表示を行うコンポーネントを作ります(リスト5)。記事の内容を取得するには、/item/{item-id}.jsonエンドポイントを利用します。
function App() {
const { data, error } = useSWR(
'https://hacker-news.firebaseio.com/v0/topstories.json',
fetcher
);
const [itemId, setItemId] = useState(null); // (3)
// 省略
return (
<div>
<h1>新着記事のID一覧</h1>
<ul>
{top10items.map((itemId) => (
<li><button onClick={() => setItemId(itemId)}>{itemId}</button></li>{/* (2) */}
))}
<li><button onClick={() => setItemId(null)}>リセット</button></li>
</ul>
<hr />
{itemId ? (
<Item itemId={itemId} />{/* (4) */}
) : (
<p>記事IDをクリックしてください</p>
)}
</div>
);
}
function Item(props) { // (1)
const { data, error } = useSWR(
`https://hacker-news.firebaseio.com/v0/item/${props.itemId}.json`,
fetcher
);
if (error) {
console.error(error);
return <div>エラーが発生しました。コンソールを確認してください。</div>
}
if (!data) {
return <div>読み込み中...</div>
}
return (
<div>
<h2>{data.title}</h2>
<p>
<a href={data.url}>{data.url}</a>
</p>
</div>
);
}
記事の内容を表示するために(1)のItemコンポーネントを作成しました。アクセス先とレスポンスのデータ構造が違うだけで、処理の構造としてはリスト4で実施したものとほとんど変わりません。Appコンポーネントでは、(2)で記事をクリックできるようにし、(3)で選択状態を保持できるようにしました。その選択状態に応じて、(4)でItemコンポーネントを描画しています。Itemコンポーネントが描画されるタイミングでuseSWRが実行され、データ取得が始まります。
さて、リスト5を実行して、記事IDをクリックしてみましょう(図3)。
一瞬だけ「読み込み中...」が表示された後、記事のタイトルとURLが表示されました。他の記事のボタンを押した後で元の記事のボタンを押すと、今度はキャッシュが効いて「読み込み中...」が表示されずに瞬時に表示されるはずです。
ここで、Chrome Dev Toolsなどで通信状況を確認すると、SWRのユニークな点が見られます(図4)。
useSWRの戻り値としてはキャッシュを返していますが、直後にキャッシュを再検証するためにバックグラウンドで通信を行っています。この挙動によって、UIのパフォーマンスとデータの鮮度を両立しているのです。
SWRに設定できること
useSWRの第三引数には各種オプションを設定できます(リスト6)。
useSWR('/api/user', fetcher, {
refreshInterval: 0,
});
オプションに設定できる項目は表1の通りです。
| プロパティ | 解説 |
|---|---|
| suspense | React.Suspenseで利用できるモードで動作する |
| fetcher | データ取得ライブラリの使い方を設定する(第二引数と同じ) |
| initialData | dataの初期値を設定する |
| revalidateOnMount | コンポーネントがマウントされたときにデータを再検証するか |
| revalidateOnFocus | ウィンドウにフォーカスが当たったときにデータを再検証するか |
| revalidateOnReconnect | ブラウザがネットワークに再接続したときにデータを再検証するか |
| refreshInterval | データの自動更新頻度(0で無効) |
| refreshWhenHidden | ウィンドウが隠れたときに自動更新を続けるかどうか |
| refreshWhenOffline | オフラインになったときに自動更新を続けるかどうか |
| shouldRetryOnError | 通信エラーの際にリトライを試みるか |
| dedupingInterval | この期間内に再実行されたら通信を行わない |
| focusThrottleInterval | この期間内には一度だけしかデータ更新を行わない |
| loadingTimeout | タイムアウト時間 |
| errorRetryInterval |
通信エラーの際のリトライを行う間隔
|
| errorRetryCount | 通信エラーの際のリトライを行う回数の上限 |
| onLoadingSlow(key, config) | リクエストに時間がかかっている場合のコールバック |
| onSuccess(data, key, config) | 通信成功時のコールバック |
| onError(err, key, config) | エラー発生時のコールバック |
| onErrorRetry(err, key, config, revalidate, revalidateOps) | リトライ発生時のコールバック |
| compare(a, b) | 再取得したデータを既存のキャッシュデータと比較する関数 |
| sPaused() |
データの再検証を中断する条件を設定する |
別のウィンドウやアプリを操作した後で戻ってきた際にもデータの再検証をするようにできるので、画面上のデータを可能な限り最新に保つようなアプリケーションも容易に作成できそうです。
共通の設定をする
オプションの設定が複雑になってきた場合、全てのuseSWRに同じオプションを設定するのが手間になってきます。その場合は、SWRConfigを上位にかぶせておくことで、下位のコンポーネントで利用されたuseSWRのデフォルト設定を変更することができます(リスト7)。
import { SWRConfig } from 'swr';
function App () {
return (
<SWRConfig
value={{
refreshInterval: 3000,
}}
>
<Articles />
</SWRConfig>
)
}
個々のuseSWRの実行時にオプションを上書きすることもできますので、柔軟に運用するとよいでしょう。
まとめ
今回は、SWRについて解説しました。通信処理のキャッシュ運用は面倒なものですが、うまく扱えればUIの体感速度を向上できる武器になるので、試してみてはいかがでしょうか。次回はUIライブラリのMaterial UIについて解説します。
