はじめに
Reactアプリケーションでデータ取得を効率的に行うことは、パフォーマンスとユーザー体験を向上させる上で非常に重要です。その中で、Vercel社が開発したuseSWRは、データフェッチングを簡単かつ強力に行えるReact Hooksライブラリとして注目を集めています。
useSWRとは何か
useSWRは「stale-while-revalidate」というHTTPキャッシュ無効化戦略に基づいたデータフェッチングライブラリです。このライブラリを使用すると、コンポーネント内でデータの取得、キャッシング、そして自動的な再検証を簡単に実装できます。
主な特徴は以下の通りです:
- キャッシュからのデータ即時返却
- バックグラウンドでの自動再検証
- インターバルによる定期的なポーリング
- フォーカス時の再検証
- ネットワーク回復時の自動再試行
なぜuseSWRを使うべきか
useSWRを使用する主な利点は以下のとおりです:
-
高速で軽量: キャッシュを活用することで、アプリケーションの応答性が向上します
-
自動更新: フォーカスやネットワーク回復時に自動的にデータを再検証するため、常に最新のデータを表示できます
-
簡潔なコード: 複雑なデータフェッチングロジックを数行のコードで実装できます
-
エラーハンドリングの簡素化: ネットワークエラーや読み込み状態の管理が容易になります
-
Suspenseとの互換性: React Suspenseと組み合わせて使用することができます
useSWRを使用することで、開発者はデータフェッチングの複雑さを抽象化し、ビジネスロジックやUIの構築に集中できます
useSWRの基本的な使い方
インストール方法
pnpm add swr
npm i swr
基本的な構文
- データを取得する非同期関数
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)
- 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リクエストと違い、サバーからプッシュされてくるデータを扱います。
株価やライブチャットなどに有効とされています
- 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のタイムラインのような機能)
- 現在のページインデックスと前のページのデータを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というライブラリもあるそうです。
この違いについては、また別の機会に学習してみようと思います。
参考リソース
公式ドキュメント