CodeZine(コードジン)

特集ページ一覧

React Hooks向けライブラリSWRとは? 通信とキャッシュ管理を簡便に

現場で役立つ! React向けライブラリ詳説 第3回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2021/04/12 11:00
目次

SWRを使ってみる

 それでは、実際の使い方を見ていきましょう。実物のAPIを使ったほうがわかりやすいので、サンプル内ではソーシャルニュースサイトHacker NewsのAPIであるHacker News APIを利用します。

 まずはcreate-react-appで作成した環境に、SWRをインストールします(リスト3)。

[リスト3]SWRをインストールする
$ npm install swr

 これだけで準備は完了です。それではまず、単純なデータを返すAPIにアクセスしてみましょう。Hacker News APIの/topstories.jsonエンドポイントは、人気の記事一覧をnumber型の記事IDの配列で返します。ひとまずこれの上位10件を並べてみましょう(リスト4)。

[リスト4]新着記事のIDを表示する
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の通りになります。

図2:通信結果を表示できた
図2:通信結果を表示できた

 IDの一覧が表示されましたね。次は、記事のIDをクリックしたときにタイトルとURLが現れるようにしてみましょう。記事のIDをpropsで受け取って、データの取得と表示を行うコンポーネントを作ります(リスト5)。記事の内容を取得するには、/item/{item-id}.jsonエンドポイントを利用します。

[リスト5]記事の内容を表示する
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)。

図3:記事の内容を表示する
図3:記事の内容を表示する

 一瞬だけ「読み込み中...」が表示された後、記事のタイトルとURLが表示されました。他の記事のボタンを押した後で元の記事のボタンを押すと、今度はキャッシュが効いて「読み込み中...」が表示されずに瞬時に表示されるはずです。

 ここで、Chrome Dev Toolsなどで通信状況を確認すると、SWRのユニークな点が見られます(図4)。

図4:バックグラウンドでキャッシュを更新している
図4:バックグラウンドでキャッシュを更新している

 useSWRの戻り値としてはキャッシュを返していますが、直後にキャッシュを再検証するためにバックグラウンドで通信を行っています。この挙動によって、UIのパフォーマンスとデータの鮮度を両立しているのです。

SWRに設定できること

 useSWRの第三引数には各種オプションを設定できます(リスト6)。

[リスト6]オプションの例
useSWR('/api/user', fetcher, {
  refreshInterval: 0,
});

 オプションに設定できる項目は表1の通りです。

表1:SWRのオプション
プロパティ 解説
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)。

[リスト7]共通の設定を行う
import { SWRConfig } from 'swr';

function App () {
  return (
    <SWRConfig 
      value={{
        refreshInterval: 3000,
      }}
    >
      <Articles />
    </SWRConfig>
  )
}

 個々のuseSWRの実行時にオプションを上書きすることもできますので、柔軟に運用するとよいでしょう。

まとめ

 今回は、SWRについて解説しました。通信処理のキャッシュ運用は面倒なものですが、うまく扱えればUIの体感速度を向上できる武器になるので、試してみてはいかがでしょうか。次回はUIライブラリのMaterial UIについて解説します。



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:現場で役立つ! React向けライブラリ詳説

著者プロフィール

  • WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5