7.3 データフェッチ — TanStack Query / SWR
案件で React を使うとき、ほぼ必ず API からデータを取ってきて画面に出します。一見すると useEffect の中で fetch すれば済みそうに見えますが、実務ではこの素朴な実装で 早い段階に 壁にぶつかります。
ローディング表示、エラーハンドリング、タブを切り替えた後の再取得、他コンポーネントとのキャッシュ共有、リトライ、楽観的更新 — これらは一見似たような話題に見えますが、それぞれ 固有の設計問題 を含んでいます。全部自前で面倒を見ようとすると、アプリの他のロジックよりもこの部分のコードの方が膨らんでしまう、ということが起こります。
この問題領域を「サーバー状態」と呼び、クライアント状態(7.2 状態管理 でやりましたね)と分けて、専用ライブラリで扱うのが定跡です。代表的な選択肢が TanStack Query と SWR の 2 つです。
サーバー状態 vs クライアント状態
TanStack Query の公式 Overview は、この区別を明確に述べています。サーバー状態は次の性質を持つ、というのがポイントです。
- リモートサーバーにあり、開発者のコントロール外に存在します。
- 取得・更新は非同期で走ります。
- 他ユーザーの操作で、予告なく変わる可能性があります。
- 時間とともに古く(stale に)なっていきます。
これは「コンポーネントが自分で持つローカル state」の 反対側 の概念です。キャッシュ、再検証、バックグラウンド同期、リクエスト重複排除、メモリ管理のような関心ごとが自動的に生まれます。これを手で書こうとするとすぐ破綻するので、専用ライブラリが存在しているわけです。
図 7.3.1 サーバー状態は「自分で持てない」から難しい
左はブラウザの中の状態です。書き換えは一瞬で、失敗もしません。右はサーバーにあるデータで、ネットワーク越しのやりとりなので、失敗したり、知らないうちに他の誰かが更新していたり、古くなっていたりします。この「性質の違い」を吸収してくれるのがデータフェッチライブラリです。
図 7.3.2 サーバー状態を扱う関心ごと
TanStack Query
TanStack Query(旧 React Query)は TanStack が公開している、多フレームワーク対応のデータ取得ライブラリです。React 版は @tanstack/react-query で提供されます。公式 Overview は「fetching, caching, synchronizing and updating server state in your web applications a breeze」と位置づけています。
基本 3 概念
公式 Quick Start は 3 つの基礎概念を挙げています。
- Queries — データの取得。
- Mutations — データの変更(POST / PATCH / DELETE など)。
- Query Invalidation — キャッシュを無効化して再取得をトリガーする。
最小例
// tanstack.com/query/latest/docs/framework/react/quick-start のパターンを最小化
import {
QueryClient,
QueryClientProvider,
useQuery,
useMutation,
useQueryClient,
} from "@tanstack/react-query";
const queryClient = new QueryClient();
type Todo = { id: number; title: string };
async function fetchTodos(): Promise<Todo[]> {
const res = await fetch("/api/todos");
if (!res.ok) throw new Error("failed");
return res.json();
}
function Todos() {
const qc = useQueryClient();
const { data, isLoading, error } = useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
});
const { mutate } = useMutation({
mutationFn: async (title: string) => {
await fetch("/api/todos", { method: "POST", body: JSON.stringify({ title }) });
},
onSuccess: () => {
qc.invalidateQueries({ queryKey: ["todos"] });
},
});
if (isLoading) return <p>読み込み中</p>;
if (error) return <p>エラー</p>;
return (
<div>
<ul>{data?.map((t) => <li key={t.id}>{t.title}</li>)}</ul>
<button onClick={() => mutate("new todo")}>追加</button>
</div>
);
}
export function App() {
return (
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
);
}主要機能
- キャッシュ:
queryKeyをキーにして結果をメモリ上に保持します。同じキーへの並行リクエストは重複排除されます。 - 自動再取得: タブにフォーカスが戻った、ネットワークが復帰した、などのタイミングで自動的に再取得します(
refetchOnWindowFocusなどで制御可能)。 - リトライ: 失敗時に既定でリトライします。
- Mutations + invalidateQueries: 変更後に関連キーのキャッシュを無効化して再取得します。
- DevTools: React Query Devtools でキャッシュ状態を可視化できます(別パッケージ)。
SWR
SWR は Vercel が公開している React 専用のデータ取得ライブラリです。名前は HTTP キャッシュ戦略 stale-while-revalidate の頭字語で、「キャッシュされた(stale な)データをすぐ返しながら、背後で再検証(revalidate)する」振る舞いを意味しています。
公式は「SWR is a minimal API with built-in caching, revalidation, and request deduplication」と要約しています。TanStack Query よりさらに API が小さく、学習コストが低いのが特徴です。
最小例
// swr.vercel.app/docs/getting-started のパターンを最小化
import useSWR from "swr";
type User = { name: string };
const fetcher = (url: string) => fetch(url).then((r) => r.json());
function Profile() {
const { data, error, isLoading } = useSWR<User>("/api/me", fetcher);
if (isLoading) return <p>読み込み中</p>;
if (error) return <p>エラー</p>;
return <p>{data?.name}</p>;
}返り値は data / error / isLoading の 3 本柱で、これで分岐を書くのが基本形です。
主要機能
- リクエスト重複排除: 同じキーを別コンポーネントで使っても、裏では 1 回のリクエストに統合されます。
- フォーカス時再検証: タブに戻ったときに自動で再取得します(オプションで無効化可能)。
- ネットワーク復帰時再検証: オフラインからオンラインに戻った際に再取得します。
- エラー時の自動リトライ。
- React Suspense 対応(公式トップで明記されています)。
図 7.3.3 stale-while-revalidate の動き
TanStack Query と SWR の選び分け
機能面は重なる部分が多く、どちらを選んでも日常的なユースケースはカバーできます。選定軸になる差分を挙げておきます。
| 観点 | TanStack Query | SWR |
|---|---|---|
| 開発元 | TanStack(多フレームワーク対応) | Vercel |
| API 面 | 広い(useQuery / useMutation / useInfiniteQuery / prefetch など) | 小さい(useSWR 中心) |
| Mutation 統合 | useMutation + invalidateQueries が公式パターン | mutate 関数で明示的にキャッシュ更新 |
| DevTools | 専用 Devtools パッケージ | 公式の専用 DevTools は提供されていません(公式サイト swr.vercel.app 上に DevTools の記載はありません) |
| Next.js との親和性 | どちらも使える。Next.js を Vercel で動かす案件では SWR が選ばれやすい傾向 | 同上 |
「細かいキャッシュ操作や大量の API を扱う」なら TanStack Query、「シンプルに済ませたい / Next.js + Vercel の構成」なら SWR、というのが現場でよく見られる分かれ方です。
Next.js App Router では fetch + cache で代替できます
Part 5 で触れたように、Next.js App Router の Server Components では fetch が拡張されていて、Next.js 側でキャッシュと再検証を扱います(5.3 Next.js App Router 入門 参照)。つまり、次のような使い分けになります。
- Server Components 内のデータ取得 → Next.js の
fetch+cache/revalidateを使います。ネットワーク往復がサーバー内で閉じるため、クライアントにライブラリを配布する必要がありません。 - Client Components 内のデータ取得(ユーザー操作に追随して何度も取る場合)→ TanStack Query / SWR を使います。フォーカス時再検証やミューテーション後の再取得など、UI 連動のキャッシュ管理が欲しいケースです。
「全部 TanStack Query で統一」も「全部 Next.js fetch に寄せる」もどちらも成立します。案件の既存コードに合わせるのが実務的な解です。