Skip to content

7.3 データフェッチ — TanStack Query / SWR ​

案件で React を使うとき、ほぼ必ず API からデータを取ってきて画面に出します。一見すると useEffect の中で fetch すれば済みそうに見えますが、実務ではこの素朴な実装で 早い段階に 壁にぶつかります。

ローディング表示、エラーハンドリング、タブを切り替えた後の再取得、他コンポーネントとのキャッシュ共有、リトライ、楽観的更新 — これらは一見似たような話題に見えますが、それぞれ 固有の設計問題 を含んでいます。全部自前で面倒を見ようとすると、アプリの他のロジックよりもこの部分のコードの方が膨らんでしまう、ということが起こります。

この問題領域を「サーバー状態」と呼び、クライアント状態(7.2 状態管理 でやりましたね)と分けて、専用ライブラリで扱うのが定跡です。代表的な選択肢が TanStack Query と SWR の 2 つです。

サーバー状態 vs クライアント状態 ​

TanStack Query の公式 Overview は、この区別を明確に述べています。サーバー状態は次の性質を持つ、というのがポイントです。

  • リモートサーバーにあり、開発者のコントロール外に存在します。
  • 取得・更新は非同期で走ります。
  • 他ユーザーの操作で、予告なく変わる可能性があります。
  • 時間とともに古く(stale に)なっていきます。

これは「コンポーネントが自分で持つローカル state」の 反対側 の概念です。キャッシュ、再検証、バックグラウンド同期、リクエスト重複排除、メモリ管理のような関心ごとが自動的に生まれます。これを手で書こうとするとすぐ破綻するので、専用ライブラリが存在しているわけです。

図 7.3.1 サーバー状態は「自分で持てない」から難しい ​

ブラウザ(ローカル state)useState で即時書き換え自分がオーナー同期的・失敗しないサーバー(API データ)非同期 / 失敗する他ユーザーで勝手に変わる時間とともに古くなるfetch / retrycache / revalidate

左はブラウザの中の状態です。書き換えは一瞬で、失敗もしません。右はサーバーにあるデータで、ネットワーク越しのやりとりなので、失敗したり、知らないうちに他の誰かが更新していたり、古くなっていたりします。この「性質の違い」を吸収してくれるのがデータフェッチライブラリです。

図 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 つの基礎概念を挙げています。

  1. Queries — データの取得。
  2. Mutations — データの変更(POST / PATCH / DELETE など)。
  3. Query Invalidation — キャッシュを無効化して再取得をトリガーする。

最小例 ​

tsx
// 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 が小さく、学習コストが低いのが特徴です。

最小例 ​

tsx
// 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 の動き ​

時間 →画面表示キャッシュ済みstale を即返却(ちょっと古いかも)ユーザーは即座に見られるローディングは出さない裏で revalidateサーバーに問い合わせ鮮度が違えば画面更新同じならそのまま(ちらつかない)体感速度と鮮度を両立

TanStack Query と SWR の選び分け ​

機能面は重なる部分が多く、どちらを選んでも日常的なユースケースはカバーできます。選定軸になる差分を挙げておきます。

観点TanStack QuerySWR
開発元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 に寄せる」もどちらも成立します。案件の既存コードに合わせるのが実務的な解です。

図 7.3.4 データフェッチの選定フロー ​

参照ソース ​