【useSWR ①入門編】基本的な使い方とメリットをまとめてみた

## はじめに

泣く子も黙る [Vercel](https://vercel.com/about) 社によって開発された、シンプルかつ強力なデータ取得のための React Hooks ライブラリ、**`useSWR`** をご存知ですか？

https://swr.vercel.app/ja

`useSWR` を使うことで、データをキャッシュに保持しながらバックグラウンドで更新する仕組みを簡単に実装することができ、アプリのパフォーマンスはもちろん、コードの可読性やメンテナンス性も大幅に向上させることができます。

`useSWR` は個人的に気になる技術なので、複数回に分けて記事を書いていくつもりです。
今回の記事では入門編として、以下のトピックについて解説しています。

- そもそも SWR とは？
- `useSWR` の基本的な使い方
- 使うことで得られるメリット
- おまけ：公式ドキュメントに PR を送った話（New：マージされました！）

では早速見ていきましょう！

## 1\. そもそも SWR とは？

SWR とは、Web におけるキャッシュ戦略の１つで、"**S**tale-**W**hile-**R**evalidate" の頭文字を取った単語です。

Stale は「古臭い」という意味があり、まさに「**古いデータを使いながら再検証する**」というニュアンスが名前からも読み取れますよね。

SWR の仕組みはシンプルですが、とても効果的で、実際に Google でも SWR によって広告配信のパフォーマンスが改善したそうです。

https://web.dev/case-studies/ads-case-study-stale-while-revalidate?hl=ja#live-example

Google にとって広告配信は収益の柱ですから、それを裏で支えている SWR は現代の Web においていかに重要な技術であるかが分かりますね！

そんな SWR は具体的には、以下のステップでデータを取得・管理します。

**1\. キャッシュから古いデータを取得・表示（Stale）**

- ユーザーがデータを要求した際、最初にキャッシュから古いデータを取得します。これにより、即座にコンテンツを表示できるため、レスポンス速度は上がり、UX も向上します

**2\. サーバーから新しいデータを取得（While）**

- 古いデータを表示している間に、バックグラウンドで非同期に最新のデータをサーバーから取得します。この非同期性が SWR のポイントです

**3\. キャッシュに新しいデータを書き込む（Revalidate）**

- 新しいデータが取得されると、キャッシュにそのデータを書き込みます。これにより次回からのデータ要求時には、更新されたキャッシュから最新のデータが提供されます

これらの手間がかかるステップを、継続的かつ自動的に簡単に呼び出せるようにしたのが `useSWR` です。次に、その基本的な使い方について詳しく見ていきましょう。

## 2\. `useSWR` の基本的な使い方
### インストール

```
npm i swr
```
もしくは
```
yarn add swr
```

### 基本的なシグネチャ

```tsx
// fetcher の典型例
const fetcher = (...args) => fetch(...args).then(res => res.json())

const { data, error, isLoading } = useSWR(key, fetcher, options)
```

#### `useSWR` の引数

- key: データの一意な識別子（通常は API の URL）
- fetcher（任意）: データを返す非同期関数。fetch や axios をラップしたもの
- options（任意）: オプション。多数用意されている


#### `useSWR` の返り値

- data: fetcher によって取得したデータ
- error: fetcher によって投げられたエラー
- isLoading: データの取得状況。bool 値

**通常のリクエストには、"loading" 、 "ready" 、 "error" の3つの状態があります。**

そのため、返り値の data, error, isLoading を使ってリクエストの現在の状態を判断し、ロジックや UI を分けることができます。

`useSWR` の引数と返り値を理解したところで、全体のコードを見ていきましょう。

```tsx
import useSWR from 'swr';

const fetcher = (...args) => fetch(...args).then(res => res.json())

function Profile({ id }) {
  const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher);

  if (error) return <div>Failed to load</div>;
  if (isLoading) return <div>loading...</div>;
  
  // データをレンダリングする
  return <div>hello {data.name}!</div>;
}
```

このコードでは、以下のような流れでデータを管理しています。

1. `useSWR` を使って /api/user/${id} からユーザーデータをフェッチします
2. fetcher 関数を使用して、fetch API でデータを取得します
3. データのフェッチが失敗した場合、エラーメッセージを表示します
4. データがまだロードされていない場合、ローディングメッセージを表示します
5. データの取得が成功した場合、ユーザーの名前を表示します

## 3\. 使うことで得られるメリット

正直多すぎます。特に機能的な優位性については[公式ドキュメント](https://swr.vercel.app/ja)に説明を譲ります。

ここでは私が感じた実装上のメリットを１つ取り上げます。

それは、**データフェッチの簡易化と各コンポーネントの独立性の向上です**。

従来の方法では、トップレベルのコンポーネントで `useEffect` を使ってデータを取得し、props を介して子・孫コンポーネントに渡す必要がありました。

- 従来の方法

```tsx
// 親コンポーネント
function Page() {
  const [user, setUser] = useState(null);

  // 子コンポーネントに渡すためだけに、ユーザー情報を取得している
  useEffect(() => {
    fetch('/api/user')
      .then(res => res.json())
      .then(data => setUser(data));
  }, []);

  if (!user) return <Spinner />;

  return (
    <div>
      <Navbar user={user} />
    </div>
  );
}

// 子コンポーネント。孫コンポーネントに props を渡すだけ
function Navbar({ user }) {
  return (
    <div>
      ...
      <Avatar user={user} />
    </div>
  );
}

// 孫コンポーネント。ここに来るまでに２つのコンポーネントが経由されている
function Avatar({ user }) {
  return <img src={user.avatar} alt={user.name} />;
}
```

この方法では、すべてのデータをトップレベルのコンポーネントに保持し、ツリーの奥深くにあるコンポーネントに props を渡していく必要があります。これはコードの保守性やコンポーネントの独立性を低下させます。

SWR はその問題を完璧に解決してくれます。`useUser` フックを作成し、各コンポーネント内で直接データをフェッチするようにリファクタリングします。

- `useSWR` を使う方法

```tsx
import useSWR from 'swr';

const fetcher = (...args) => fetch(...args).then(res => res.json());

// useSWR をラップしたユーザー情報を取得するカスタムフック
function useUser() {
  const { data, error } = useSWR('/api/user', fetcher);
  return {
    user: data,
    isLoading: !error && !data,
    error: error
  };
}

// 親コンポーネント
// ここではユーザー情報は扱わないので呼び出す必要がない
function Page() {
  return (
    <div>
      <Navbar />
    </div>
  );
}

// 子コンポーネント。同様
function Navbar() {
  return (
    <div>
      ...
      <Avatar />
    </div>
  );
}

// 実際にユーザー情報を扱うコンポーネント
function Avatar() {
  const { user, isLoading } = useUser(); // ここで呼び出す!
  
  if (isLoading) return <Spinner />;
  return <img src={user.avatar} alt={user.name} />;
}
```
この方法では、以下のメリットがあります：

- **実装が楽になる**: トップレベルのコンポーネントでデータフェッチを行い、props を介してデータを渡す手間がなくなります
- **コンポーネントの独立性が高まる**: 各コンポーネントが自身でデータを管理するため、他のコンポーネントとの依存関係が減ります

そして、最も素晴らしいのは、

- **API リクエストの最適化**: 同じ SWR キーを使用することで、API リクエストが自動的に重複排除され、キャッシュが共有されます。これにより、複数のコンポーネントで同じデータを必要とする場合でも、API へのリクエストは一度だけで済みます

このように、`useSWR` を使うことで多くのメリットを享受できます。

## 4\. おまけ：公式ドキュメントに PR を送った話

個人的な話になりますが、最近 OSS にコントリビュートしたい欲が高まっており、OSS 初心者に手頃そうなリポジトリはないかと GitHub を探し回っていました。そんなときに見つけたのが、今回紹介した `useSWR` 公式ドキュメントの[未翻訳のページ](https://swr.vercel.app/ja/docs/with-nextjs)です。

そして、実際にこのページを英語から日本語に翻訳して PR を送ってみました。（2024.5.25）

https://github.com/vercel/swr-site/pull/580

無事マージされるかどうか分かりませんが、今後 `useSWR` について記事を書いていく際は、この件についても情報をアップデートしていければと思います。また、初めて OSS に参加したことについても、その背景や作業の流れなどを別で記事にまとめるつもりです。

追記：2024.5.26
無事にマージされました！下記のページを翻訳したのでよろしければご覧ください👇

https://swr.vercel.app/ja/docs/with-nextjs

さらに追記：2024.5.29
OSS に参加した経緯や、経験から得た学びなどを詳しくまとめたのでこちらもよろしければご覧ください。

https://qiita.com/Teradad41/items/ddffdf94ca9e27abf40d
 
## おわりに

`useSWR` の入門編いかがだったでしょうか。

それにしても Vercel 社は Next.js をはじめ、たくさんの素晴らしい技術を私達に提供してくれていますよね。フロントエンドエンジニアは Vercel 社には足を向けて寝られません orz。

次回以降の内容は未定ですが、 `useSWR` のより詳細な機能について解説していこうと思います。

ご覧いただきありがとうございました！

## 参考

- SWR 公式ドキュメント

https://swr.vercel.app/ja

- SWR GitHub リポジトリ

https://github.com/vercel/swr

- Qita 記事

https://qiita.com/ke_na/items/567ab27ac91a4d6be690

- Web.dev

https://web.dev/case-studies/ads-case-study-stale-while-revalidate?hl=ja#live-example

- SWR 良記事

https://blog.jxck.io/entries/2016-04-16/stale-while-revalidate.html#web-%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5