Reactのデータフェッチライブラリ「SWR」と「TanStack Query」の違いを比較してみた
ReactでAPIからデータを取得する際、便利なライブラリとして「SWR」と「TanStack Query(旧React Query)」があります。
どちらもAPI通信やキャッシュ管理を簡単に実装できるライブラリですが、具体的に何が違うのでしょうか?
今回は、それぞれの基本的な使い方から、キャッシュの仕組み、データの再取得、設定値の違いまで、コードを交えて比較していきます。
1. SWRとTanStack Queryとは?
SWR
SWRはVercelが開発しているReact向けのデータフェッチライブラリです。
シンプルなAPIでデータ取得やキャッシュ、再検証などを実装できることが特徴です。
npm install swr
TanStack Query(旧React Query)
TanStack Queryは、非同期データの取得やキャッシュ、更新などを管理するためのライブラリです。
旧React Queryという名前で知られており、現在はReact以外のフレームワークにも対応しています。
npm install @tanstack/react-query
2. なぜこれらのライブラリが必要なのか?
例えば、Reactからユーザー一覧を取得する場合、通常は次のように実装できます。
import { useState, useEffect } from "react";
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("/api/users")
.then((res) => res.json())
.then((data) => setUsers(data))
.finally(() => setLoading(false));
}, []);
if (loading) return <p>読み込み中...</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
この方法でもAPIからデータを取得できます。
しかし、実際のアプリケーションでは次のような課題が出てきます。
- 同じAPIを複数のコンポーネントから呼び出してしまう
- 一度取得したデータを別の画面でも再利用したい
- データが古くなったら自動的に再取得したい
- API通信中の状態やエラーを管理したい
- データを更新したときに、関連する画面にも反映したい
こうした処理を効率的に管理できるのがSWRとTanStack Queryです。
3. データ取得の書き方を比較する(GET)
今回は、GET /api/usersからユーザー一覧を取得する例で比較します。
SWRの場合
import useSWR from "swr";
const fetcher = (url) =>
fetch(url).then((res) => res.json());
function UserList() {
const { data, error, isLoading } = useSWR(
"/api/users",
fetcher
);
if (isLoading) return <p>読み込み中...</p>;
if (error) return <p>エラーが発生しました</p>;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
useSWRの第1引数にキャッシュを識別するキー、第2引数にデータを取得する関数を指定します。
TanStack Queryの場合
import { useQuery } from "@tanstack/react-query";
const fetchUsers = async () => {
const res = await fetch("/api/users");
if (!res.ok) {
throw new Error("取得に失敗しました");
}
return res.json();
};
function UserList() {
const {
data,
error,
isPending
} = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
});
if (isPending) return <p>読み込み中...</p>;
if (error) return <p>エラーが発生しました</p>;
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
TanStack Queryでは、useQueryにオブジェクト形式で設定を渡します。
queryKeyがキャッシュを識別するキー、queryFnがデータを取得する関数です。
書き方の違い
| 比較項目 | SWR | TanStack Query |
|---|---|---|
| フック | useSWR | useQuery |
| キャッシュ識別子 | Key | queryKey |
| データ取得関数 | fetcher | queryFn |
| 読み込み状態 | isLoading | isPending |
| データ | data | data |
| エラー | error | error |
どちらも基本的なデータ取得の書き方は似ています。
TanStack QueryではqueryKeyを配列形式で指定することが特徴です。
例えば、ユーザーIDごとにデータをキャッシュしたい場合は、次のように指定できます。
useQuery({
queryKey: ["users", userId],
queryFn: () => fetchUser(userId),
});
これにより、ユーザーIDごとに異なるキャッシュとして管理できます。
4. キャッシュの有効期間の違い
SWRとTanStack Queryには、キャッシュやAPI通信を制御するための設定があります。
その中でも、よく比較されるのが次の2つです。
- SWR:dedupingInterval
- TanStack Query:staleTime
一見すると似た設定ですが、実際には役割が異なります。
SWR:dedupingInterval
dedupingIntervalは、同じキーに対するリクエストの重複を一定時間抑制するための設定です。
例えば、30秒に設定した場合です。
const { data } = useSWR(
"/api/users",
fetcher,
{
dedupingInterval: 30000,
}
);
同じキーに対して短時間に複数のリクエストが発生した際、重複した通信を抑制します。
| 時刻 | 動作 |
|---|---|
| 0秒 | APIリクエストを実行 |
| 10秒 | 同じキーのリクエストを抑制 |
| 20秒 | 同じキーのリクエストを抑制 |
| 35秒 | 重複抑制期間が終了しているため再リクエストが可能 |
重要なのは、30秒間データが新鮮であることを保証しているわけではないという点です。
あくまで、同じキーに対するリクエストの重複を抑えるための設定です。
TanStack Query:staleTime
一方、TanStack QueryのstaleTimeは、取得したデータをどのくらいの期間「新鮮(Fresh)」とみなすかを設定するものです。
const { data } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
staleTime: 30000,
});
この場合、データ取得後30秒間はFreshとして扱われます。
30秒を超えるとStale(古い)状態になります。
| 時刻 | データの状態 |
|---|---|
| 0秒 | Fresh |
| 10秒 | Fresh |
| 20秒 | Fresh |
| 35秒 | Stale |
ここで注意したいのは、30秒経過した瞬間に自動でAPI通信が発生するわけではないということです。
Staleになったデータは、再取得の対象になります。
例えば、次のようなタイミングで再取得が発生します。
- コンポーネントがマウントされたとき
- ブラウザのウィンドウにフォーカスが戻ったとき
- ネットワークが再接続されたとき
- invalidateQueriesで明示的に無効化したとき
dedupingIntervalとstaleTimeは結局同じなのか?
ここは少し分かりにくい部分です。
どちらも設定した時間内に不要なAPI通信を抑制するような動作になるため、結果だけを見ると似ています。
しかし、内部で判断している内容が異なります。
- dedupingInterval:同じキーのリクエストが短時間に重複していないか
- staleTime:キャッシュされたデータが新鮮な状態か
つまり、前者はリクエストの重複抑制、後者はキャッシュデータの鮮度管理を目的としています。
両者とも設定時間が経過しただけで必ずAPI通信が発生するわけではありません。
また、SWRにはrevalidateOnFocusなどの再検証設定もあるため、dedupingIntervalとstaleTimeを完全に同じ機能として比較することはできません。
5. TanStack QueryにはgcTimeもある
TanStack Queryには、staleTimeとは別にgcTimeという設定があります。
const { data } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
staleTime: 30000,
gcTime: 300000,
});
それぞれの役割は次のとおりです。
| 設定 | 役割 | 設定例 |
|---|---|---|
| staleTime | データを新鮮とみなす期間 | 30秒 |
| gcTime | 未使用になったキャッシュを保持する期間 | 5分 |
例えば、ユーザー一覧を取得した後、別の画面に移動したとします。
その結果、ユーザー一覧のQueryを利用するコンポーネントがなくなると、キャッシュは非アクティブになります。
この状態から5分間利用されなければ、キャッシュが削除されます。
つまり、次のような違いがあります。
- staleTime:データが新鮮かどうか
- gcTime:使われなくなったデータをいつ削除するか
この2つは別々に管理されています。
6. データ更新の書き方を比較する(PUT)
次に、ユーザー情報を更新する場合を比較します。
SWRの場合
SWRではmutateを使って、キャッシュの再検証を行うことができます。
import { useSWRConfig } from "swr";
function UserUpdate() {
const { mutate } = useSWRConfig();
const updateUser = async () => {
await fetch("/api/users/1", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Taro",
}),
});
// ユーザー一覧のキャッシュを再検証
await mutate("/api/users");
};
return (
<button onClick={updateUser}>
更新
</button>
);
}
mutateを使うことで、指定したキーのキャッシュを再検証できます。
TanStack Queryの場合
TanStack Queryでは、useMutationを使ってデータ更新を管理できます。
import {
useMutation,
useQueryClient
} from "@tanstack/react-query";
function UserUpdate() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async () => {
const res = await fetch("/api/users/1", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Taro",
}),
});
if (!res.ok) {
throw new Error("更新に失敗しました");
}
return res.json();
},
onSuccess: () => {
// ユーザー一覧のキャッシュを無効化
queryClient.invalidateQueries({
queryKey: ["users"],
});
},
});
return (
<button onClick={() => mutation.mutate()}>
更新
</button>
);
}
TanStack Queryでは、useMutationによって更新処理を管理できます。
また、onSuccessを利用することで、更新成功後に関連するキャッシュを無効化できます。
データ更新の比較
| 比較項目 | SWR | TanStack Query |
|---|---|---|
| データ更新 | fetchなど | useMutation |
| キャッシュ操作 | mutate | QueryClient |
| キャッシュの再検証 | mutate | invalidateQueries |
| 更新中の状態 | useSWRMutationなど | isPending |
| 成功・失敗時の処理 | コールバックなど | onSuccess / onError |
なお、SWRにもuseSWRMutationという更新処理向けのフックが用意されています。
そのため、SWRではデータ更新ができないというわけではありません。
どちらもデータ更新に対応していますが、APIの設計やキャッシュ管理の方法に違いがあります。
7. まとめ
SWRとTanStack Queryは、どちらもReactアプリケーションにおけるサーバー状態の管理を効率化するライブラリです。
基本的なデータ取得やキャッシュ、データ更新などは両方で実現できます。
| 比較項目 | SWR | TanStack Query |
|---|---|---|
| データ取得 | useSWR | useQuery |
| データ更新 | useSWRMutationなど | useMutation |
| キャッシュ識別 | Key | queryKey |
| キャッシュ再検証 | mutate | invalidateQueries |
| リクエスト重複抑制 | dedupingInterval | Queryの共有・重複排除 |
| データの鮮度管理 | 再検証の仕組み | staleTime |
| キャッシュの保持・削除 | キャッシュ管理 | gcTime |
SWRの特徴
- シンプルなAPIでデータ取得を実装できる
- キャッシュや再検証の仕組みが分かりやすい
- 小〜中規模のデータ取得処理にも取り入れやすい
TanStack Queryの特徴
- データ取得と更新の役割が明確
- staleTimeやgcTimeなど、キャッシュを細かく管理できる
- Mutationやキャッシュの無効化など、複雑なデータ管理にも対応しやすい
どちらが優れているというわけではなく、アプリケーションの規模やデータ管理の複雑さに応じて選択することが重要です。