3
3

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についての学習 useEffectよりも優れたデータ取得法(useSWRInfinite、useSWRSubscription...)

Last updated at Posted at 2024-09-15

はじめに

Reactアプリケーションでデータ取得を効率的に行うことは、パフォーマンスとユーザー体験を向上させる上で非常に重要です。その中で、Vercel社が開発したuseSWRは、データフェッチングを簡単かつ強力に行えるReact Hooksライブラリとして注目を集めています。

useSWRとは何か

useSWRは「stale-while-revalidate」というHTTPキャッシュ無効化戦略に基づいたデータフェッチングライブラリです。このライブラリを使用すると、コンポーネント内でデータの取得、キャッシング、そして自動的な再検証を簡単に実装できます。

主な特徴は以下の通りです:

  • キャッシュからのデータ即時返却
  • バックグラウンドでの自動再検証
  • インターバルによる定期的なポーリング
  • フォーカス時の再検証
  • ネットワーク回復時の自動再試行

なぜuseSWRを使うべきか

useSWRを使用する主な利点は以下のとおりです:

  1. 高速で軽量: キャッシュを活用することで、アプリケーションの応答性が向上します

  2. 自動更新: フォーカスやネットワーク回復時に自動的にデータを再検証するため、常に最新のデータを表示できます

  3. 簡潔なコード: 複雑なデータフェッチングロジックを数行のコードで実装できます

  4. エラーハンドリングの簡素化: ネットワークエラーや読み込み状態の管理が容易になります

  5. Suspenseとの互換性: React Suspenseと組み合わせて使用することができます

useSWRを使用することで、開発者はデータフェッチングの複雑さを抽象化し、ビジネスロジックやUIの構築に集中できます

useSWRの基本的な使い方

インストール方法

pnpm add swr
npm i swr

基本的な構文

  1. データを取得する非同期関数
     const fetcher = (...args) => fetch(...args).then(res => res.json())
    
    // fetchを使う場合
     const fetcher = url => fetch(url).then(r => r.json())
    
    // axios を使う場合
     const fetcher = url => axios.get(url).then(res => res.data)
     
    
  2. useSWRをインポートして任意の関数コンポーネントで使用
    const { data, error, isLoading } = useSWR(key, fetcher)
    

key: データの一意な識別子(通常は API の URL)
基本的にkeyで記述したapiのurlがfetcherの引数に渡されます

シンプルな例(APIからデータを取得する)

// データ取得部分をカスタムフック化
function useUser (id) {
  const { data, error, isLoading } = useSWR(`/api/user/${id}`, fetcher)
 
  return {
    user: data,
    isLoading,
    isError: error
  }
}

function User (id) {
  const { data, error, isLoading } = useUser(id)
  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error loading user data.</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

  • 使いたい関数コンポーネント上で、カスタムフックの呼び出しのみでデータの取得ができる!
useSWRを使わず、useEffectでデータ取得を行うと
function User (id) {
  const [user, setUser] = useState(null)
  useEffect(() => {
    fetch('api/user/${id}')
      .then(res => res.json())
      .then(data => setUser(data))
  }, [])

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

コードが複雑化します。またuseSWRを使った方が簡単にデータの再取得を定義できます。

mutateとは

mutateとはキャッシュされたデータを更新するためのメソッドです
サーバーとの通信を待たずにクライアント側でデータを更新し、ユーザーに反映させることが可能です。

  • グローバルミューテート
    どんなキーに対してもミューテートできます
    import { useSWRConfig } from "swr"
     
    function App() {
      const { mutate } = useSWRConfig()
      mutate(key, data, options)
    }
    
  • バウンドミューテート
    useSWRのkeyに対応しており、フックの返り値から値を取得することができます。
    import useSWR from 'swr'
     
    function Profile () {
      const { data, mutate } = useSWR('/api/user', fetcher)
     
      return (
        <div>
          <h1>My name is {data.name}.</h1>
          <button onClick={async () => {
            const newName = data.name.toUpperCase()
            // データを更新するために API にリクエストを送信します
            await requestUpdateUsername(newName)
            // ローカルのデータを即座に更新して再検証(再フェッチ)します
            // 注意: useSWR の mutate は key が対応付けられているため、key の指定は必要ありません
            mutate({ ...data, name: newName })
          }}>Uppercase my name!</button>
        </div>
      )
    }
    
  • mutateの第二引数にfalseを指定しないと再検証が実行されるので、不要なリクエスト防止に注意!!
mutateの使用例

ツイートの投稿:
新しいツイートを投稿した後、タイムラインのデータをmutateを使って即座に更新します。これにより、ユーザーは投稿したツイートをすぐに自分のタイムラインで確認できます。

自動再検証

フォーカス時の再検証

タブの切り替え or ページのフォーカスによって最新のデータを取得するオプション。デフォルトでtrueになっている

const { data, error, isLoading } = useSWR('/api/user', fetcher, {
    revalidateOnFocus: true, // デフォルト値
  });
定期的な再検証

一定時間(設定可能)ごとにデータを自動的に再フェッチするオプション
リアルタイムデータを表示する場合に非常に便利
デフォルトでは無効になっています。ので使用の際は指定します。

const { tweet, error, isLoading } = useSWR('api/tweets', fetcher, {refreshInterval: 5000})
// 5秒ごとにデータを自動再fetch
自動再検証の無効化
useSWR(key, fetcher, {
  revalidateIfStale: false,
  revalidateOnFocus: false,
  revalidateOnReconnect: false
})

これを指定するとデータがキャッシュされると二度とリクエストされなくなるそうです。

mutateはこちらを参考にさせていただきました。

ユースケース

リアルタイムデータ更新

refreshIntervalより更にリアルタイム性が必要で、効率的なデータの取得を行うには
useSWRSubscriptionというフックを使うといいです

サブスクリプションとは

サブスクリプションは、クライアント(ブラウザ)がサーバーからリアルタイムで更新を受け取る仕組みです。従来のポーリング(定期的にデータを取得する方法)と異なり、データが変更されたときにすぐに通知を受け取ることができます

これはHTTPリクエストと違い、サバーからプッシュされてくるデータを扱います。

株価やライブチャットなどに有効とされています

  1. import
    import useSWRSubscription from 'swr/subscription'
    
  • 型定義
    useSWRSubscription<Data, Error>(key: Key, subscribe: (key: Key, options: { next: (error?: Error | null, data: Data) => void }) => () => void): { data?: Data, error?: Error }
    
  • 引数
    key :useSWRと同じでデータソースの指定やキャッシュ管理に使われます
    suscribe関数 :サブスクリプションを設定する関数
    next関数 :errorが起きたらエラーを渡して、第二引数に新しいデータを渡す
  • フックの戻り値
    Data :最新のデータ
    error :エラーオブジェクト
WebSocketのデータソースを購読する

Webソケットについては

簡単に言ったら、HTTPがリクエストに対してレスポンスを返すよに対してWebソケットはよっ!て挨拶したらデータを投げ合える感じですかねぇ......

import useSWRSubscription from 'swr/subscription'
 
function App() {
// keyが自動的にsubscribeに渡され、next関数が実施されます
  const { data, error } = useSWRSubscription('ws://localhost:8000/xxx', subscribe)
 
  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data}!</div>
}


function subscribe(key, { next }) {
  // WebSocket接続を作成
  const socket = new WebSocket(key)
  // 接続が開いて、メッセージを受信したときの処理
  socket.addEventListener('message', (event) => next(null, event.data))
  // エラーが発生したときの処理
  socket.addEventListener('error', (event) => next(event.error))
  // クリーンアップ関数
  return () => socket.close()
}

useSWRInfinite

useSWRInfinite(twitterのタイムラインのような機能)
  1. 現在のページインデックスと前のページのデータをgetKeyに渡す
const getKey = (pageIndex, previousPageData) => {
  if (previousPageData && !previousPageData.length) return null // 最後に到達した
  return `/users?page=${pageIndex}&limit=10`                    // SWR キー
}

公式より

getKey 関数は、useSWRInfinite と useSWR とで大きな違いがあります。 現在のページのインデックスに加えて、前のページのデータも受け入れます。 したがって、インデックスベースとカーソルベースの両方のページネーション API を適切にサポートできます。

  • pageIndex: 現在のページインデックス(0から始まる)
  • previousPageData: 前のページのデータ
import useSWRInfinite from 'swr/infinite'
 
// ...

const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite(
  getKey, fetcher?, options?
)
  • useSWRと引数は基本同じ

  • data: 取得したデータの配列(各要素が1ページ分のデータ)
    これはつまり

[
  [ 1ページ目のデータ ],
  [ 2ページ目のデータ ],
  [ 3ページ目のデータ ],
    ... 以降のページ
]

というデータ構造になるので取得の際は注意が必要です

  • error: エラーオブジェクト(エラーが発生した場合)
  • size: 現在取得しているページ数
  • setSize: ページ数を変更する関数(新しいデータを読み込むのに使用)

ちょっと重くなったので、続きは次回また記述します。

useSWRと他のデータフェッチライブラリとの比較

TanStack QueryやApollo Client

業務でuseSWRを使用したのでデータ取得の方法はuseSWR一強かと思っていましたが、TanStack Queryや Apollo Clientというライブラリもあるそうです。

この違いについては、また別の機会に学習してみようと思います。

参考リソース

公式ドキュメント

3
3
2

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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?