💡

SWR 基礎

2022/03/03に公開

SWRとは高速にデータを取得を行いためのReact Hooksライブラリです。
https://swr.vercel.app/ja

名前の由来と設計思想は、比較的新しいHTTPキャッシュ無効化戦略である「Stale-While-Revalidate」に大きく依存しています。(詳細後述)

この記事では、ざっくりとSWRの思想と使用方法に関して理解することを目指します。

Stale-While-Revalidateとは

まずは、Stale-While-Revalidateに関して理解を深めます。
冒頭でも記載しましたが、一言で言うとStale-While-RevalidateはHTTPキャッシュ無効化戦略です。

キャッシュはシステムのパフォーマンスにおいて非常に重要です。
しかし、その設計・扱いは実は難しく、古いキャッシュが残り続けたために、予期せぬバグを引き起こしたりします。

従来は、キャッシュの指定方法は大きく分けて下記の二つの方式がありました。

  • Cache-Control ヘッダーで expiresmax-age を使用し、クライアント側で制御を行う
  • EtagLast-Modified などを使用し、サーバー側にキャッシュの有効性を問い合わせる

これらの方法には、優先される設定などがあり、下手に組み合わせると想定通りの挙動にならず、更新されないという状態に陥ることがあります。
そこで キャッシュを使いつつ、裏側では非同期でキャッシュの更新を行う という仕組みが導入されました。これがSWRです。
Stale-While-Revalidateをざっくり訳すと「再検証しつつ古くなった(ものを使う)」というような感じになると思うので、そのままですね。

もっと厳密に知りたいという方はRFCもご覧ください。
https://datatracker.ietf.org/doc/html/rfc5861

SWRとは

Stale-While-Revalidateをざっくりと理解したところで、SWRに戻ります。
Stale-While-Revalidateの思想に基づき、SWRはfetchしたデータをキャッシュし、それ以降キャッシュを使いつつ、裏側で非同期に更新を行います。

まずは、サンプルのコードを見てみます。

// swr fetch hook
function useUser () {
  const id = ...
  const { data, error } = useSWR(
        `/api/user/${id}`,
    (args) => fetch(args).then(res => res.json())
  )

  return {
    user: data,
    isLoading: !error && !data,
    isError: error
  }
}

useSWRでデータのfetchを行います。

第一引数はfetch対象のデータの一意な識別子で、APIのパスを使用するのが一般的なようです。
この識別子により、使用するキャッシュデータが判別されます。
string・配列・null・関数を渡すことができ、nullやfalse判定される値を返す関数を渡すとfetcherが実行されません。そのため、関数を使えば、実行制御が可能です。

第二引数は実際にfetchを行う関数です。axiosももちろん使えます。

ちなみに第三引数も使え、optionsで様々な制御が可能です。
https://swr.vercel.app/docs/options#options

<SWRConfig 
  value={{
    refreshInterval: 3000,
    fetcher: (resource, init) => fetch(resource, init).then(res => res.json())
  }}
>
  <App />
</SWRConfig>

SWRConfigをTop-Levelで使用すると、グローバルな設定が可能です。

// page component
function Page () {
  return <div>
    <Navbar />
    <Content />
  </div>
}

// child components
function Navbar () {
  return <div>
    ...
    <Avatar />
  </div>
}

function Content () {
  const { user, isLoading } = useUser()
  if (isLoading) return <Spinner />
  return <h1>Welcome back, {user.name}</h1>
}

function Avatar () {
  const { user, isLoading } = useUser()
  if (isLoading) return <Spinner />
  return <img src={user.avatar} alt={user.name} />
}

コール部分も見ていきいます。

一般的には、親コンポーネントで useEffect 内でAPIコールし、子コンポーネントにpropsかcontext経由で渡して、子コンポーネント内で使用すると思います。
一方SWRを使用すると、子コンポーネント内でSWR経由で直接キャッシュされたデータを取得するので、propsのドリルダウンなどの問題が解消され、全てのコンポーネントは独立し、メンテナンス性が向上します。

そして、キャッシュを使用しているので、useUserを何箇所で呼ぼうとも、APIコールは一回に抑えられ、キャッシュされたデータはコールされた全ての箇所で共有されます。

その他の機能

  • フォーカス時の再検証
    フォーカスを行うと、再検証を行い、更新が必要であれば、最新のデータを自動的に取得します。

  • 定期的な再検証の実行
    下記のようなコードを書くと、1秒間隔でfetchを行います。
    ToDo Listをブラウザ間で同期するようなケースに使用可能です。

useSWR('/api/todos', fetcher, { refreshInterval: 1000 })

その他にもページネーションを実装するための関数が用意されていたり、
TypeScriptも対応しています。
また、Next.jsと同じチームが開発を行なっているので、 Next.jsでのSSGSSR との親和性も高いです。

Discussion