4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-05-25

はじめに

泣く子も黙る Vercel 社によって開発された、シンプルかつ強力なデータ取得のための React Hooks ライブラリ、useSWR をご存知ですか?

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

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

  • そもそも SWR とは?
  • useSWR の基本的な使い方
  • 使うことで得られるメリット
  • おまけ:公式ドキュメントに PR を送った話(New:マージされました!)

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

1. そもそも SWR とは?

SWR とは、Web におけるキャッシュ戦略の1つで、"Stale-While-Revalidate" の頭文字を取った単語です。

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

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

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

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

1. キャッシュから古いデータを取得・表示(Stale)

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

2. サーバーから新しいデータを取得(While)

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

3. キャッシュに新しいデータを書き込む(Revalidate)

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

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

2. useSWR の基本的な使い方

インストール

npm i swr

もしくは

yarn add swr

基本的なシグネチャ

// 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 の引数と返り値を理解したところで、全体のコードを見ていきましょう。

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. 使うことで得られるメリット

正直多すぎます。特に機能的な優位性については公式ドキュメントに説明を譲ります。

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

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

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

  • 従来の方法
// 親コンポーネント
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>
  );
}

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

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

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

  • useSWR を使う方法
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 公式ドキュメントの未翻訳のページです。

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

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

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

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

おわりに

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

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

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

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

参考

  • SWR 公式ドキュメント

  • SWR GitHub リポジトリ

  • Qita 記事

  • Web.dev

  • SWR 良記事

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?