はじめに
Reactでデータフェッチング、みなさんどうしてますか。
useEffectとuseStateを組み合わせて、ローディング状態を管理して、エラーハンドリングして...正直なところ、毎回同じようなコードを書くのがしんどいんですよね。
そんな悩みを解決してくれるのが、今回紹介するSWRというライブラリです。Vercel(Next.jsを作っている会社)が開発していて、GitHubスター数は32,000超え。月間ダウンロード数は約2,500万という、かなりの人気ライブラリになっています。
個人的には、一度使い始めたらもう戻れないくらい便利だと思っています。
SWRとは
SWRは「stale-while-revalidate」というHTTPキャッシュ戦略に基づいたReact用のデータフェッチングライブラリです。
この戦略、何がすごいかというと:
- まずキャッシュからデータを返す(古くても即座に表示)
- バックグラウンドで新しいデータを取得
- 取得完了したら画面を更新
つまり、ユーザーには常に「何か」が表示されている状態を保ちつつ、最新データへの更新も行えるという話です。体感的なスピードがかなり上がります。
特徴・メリット
SWRの良いところを整理してみます。
軽量でシンプル
バンドルサイズは約8.5KB(gzip圧縮時)。これ、意外と重要なんですよね。データフェッチングのためだけに重いライブラリを入れたくないので。
自動キャッシュと重複排除
同じAPIを複数コンポーネントから呼んでも、実際のリクエストは1回だけ。キャッシュも自動で管理してくれます。これだけで開発がだいぶ楽になります。
自動再検証
- タブにフォーカスが戻ったとき
- ネットワークが復旧したとき
- 一定間隔(ポーリング)
こういったタイミングで勝手にデータを最新にしてくれます。リアルタイム性が求められるアプリでも使いやすい。
TypeScript対応
型推論がしっかり効くので、TypeScriptプロジェクトとの相性が良いです。30代になって型の恩恵を強く感じるようになりました。
SSR/SSG対応
Next.jsとの相性は抜群。というか同じVercelが作っているので当然ですね。サーバーサイドでのプリフェッチもスムーズに行えます。
インストール方法
インストールはnpmかyarnで一発です。
# npm
npm install swr
# yarn
yarn add swr
# pnpm
pnpm add swr
依存関係はReact 16.11.0以上のみ。シンプルで良いですね。
基本的な使い方
最小構成
まずは一番シンプルな例から。
import useSWR from 'swr'
// fetcher関数を定義
const fetcher = (url: string) => fetch(url).then(res => res.json())
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>エラーが発生しました</div>
if (isLoading) return <div>読み込み中...</div>
return <div>こんにちは、{data.name}さん</div>
}
これだけです。useEffectもuseStateも書かなくて良い。QOL上がりますね。
グローバル設定
fetcher関数を毎回書くのは面倒なので、グローバルに設定できます。
import { SWRConfig } from 'swr'
const fetcher = (url: string) => fetch(url).then(res => res.json())
function App() {
return (
<SWRConfig value={{ fetcher }}>
<Profile />
<Dashboard />
</SWRConfig>
)
}
これで各コンポーネントではURLを渡すだけで済みます。
条件付きフェッチ
ユーザーがログインしているときだけデータを取得したい、みたいなケース。
function UserProfile({ userId }: { userId: string | null }) {
// userIdがnullのときはフェッチしない
const { data } = useSWR(userId ? `/api/users/${userId}` : null, fetcher)
if (!userId) return <div>ログインしてください</div>
if (!data) return <div>読み込み中...</div>
return <div>{data.name}</div>
}
キーにnullを渡すとフェッチをスキップしてくれます。
ミューテーション(楽観的UI)
データ更新時に、サーバーレスポンスを待たずにUIを更新する「楽観的UI」も簡単に実装できます。
import useSWR, { mutate } from 'swr'
function TodoList() {
const { data: todos } = useSWR('/api/todos', fetcher)
const addTodo = async (text: string) => {
const newTodo = { id: Date.now(), text, completed: false }
// 即座にUIを更新(楽観的更新)
mutate('/api/todos', [...todos, newTodo], false)
// 実際のAPIリクエスト
await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo)
})
// 再検証してサーバーの状態と同期
mutate('/api/todos')
}
return (
// ...
)
}
ユーザー体験がかなり良くなります。
実践的なユースケース
ページネーション
function Posts({ page }: { page: number }) {
const { data, isLoading } = useSWR(`/api/posts?page=${page}`, fetcher)
if (isLoading) return <div>読み込み中...</div>
return (
<ul>
{data.posts.map((post: Post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
ページ番号をキーに含めるだけで、ページごとにキャッシュが効きます。
無限スクロール
useSWRInfiniteを使うと無限スクロールも実装できます。
import useSWRInfinite from 'swr/infinite'
function InfiniteList() {
const getKey = (pageIndex: number, previousPageData: any) => {
if (previousPageData && !previousPageData.length) return null
return `/api/items?page=${pageIndex}`
}
const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher)
const items = data ? data.flat() : []
return (
<div>
{items.map((item: Item) => (
<div key={item.id}>{item.name}</div>
))}
<button onClick={() => setSize(size + 1)}>
もっと読み込む
</button>
</div>
)
}
ポーリング(定期更新)
リアルタイム性が必要な画面では、ポーリングも設定一つで実現できます。
function StockPrice() {
const { data } = useSWR('/api/stock/price', fetcher, {
refreshInterval: 3000 // 3秒ごとに更新
})
return <div>現在価格: {data?.price}円</div>
}
エラーリトライ
ネットワークエラー時の自動リトライも組み込まれています。
const { data } = useSWR('/api/data', fetcher, {
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
// 404はリトライしない
if (error.status === 404) return
// 最大10回まで
if (retryCount >= 10) return
// 5秒後にリトライ
setTimeout(() => revalidate({ retryCount }), 5000)
}
})
React Query(TanStack Query)との比較
よく比較されるReact Queryとの違いについても触れておきます。
| 項目 | SWR | React Query |
|---|---|---|
| バンドルサイズ | 約8.5KB | 約13KB |
| 機能の豊富さ | シンプル | 多機能 |
| 学習コスト | 低い | やや高い |
| DevTools | なし | あり |
個人的には、シンプルなプロジェクトならSWR一択ですね。React Queryは高機能だけど、その分複雑になりがちです。必要十分な機能で軽量なSWRは、多くのケースで最適解だと思います。
まとめ
SWRを使うと、Reactでのデータフェッチングがかなり楽になります。
- キャッシュ戦略が秀逸 - stale-while-revalidateで体感速度アップ
- コード量が減る - useEffect + useStateの定型コードから解放
- 自動再検証が便利 - フォーカス時やネットワーク復旧時に勝手に更新
- 軽量 - 8.5KBで必要十分な機能
- TypeScript対応 - 型推論がしっかり効く
正直なところ、新規のReactプロジェクトでSWRを使わない理由がないくらいです。まだ使ったことがない方は、ぜひ試してみてください。時短になるし、コードもスッキリしますよ。