TanStack Query (旧名 React Query) は、非同期サーバーの状態を管理するためのライブラリです。
主なソリューション:
- サーバーのデータ取得。
- リクエストの状態管理。
- データのキャッシュと再利用。
- 自動的に再リクエストします。
- 重複排除を要求します。
- サーバーデータの変更。
- 更新と無効化をキャッシュします。
- ページネーションと無限スクロール。
Django + DRF + Orval + Axios + React プロジェクトの場合:
Django / DRF
↑
Axios
↑
Orval Generated API Function
↑
TanStack Query
↑
Orval Generated Hooks
↑
React Components
区別する必要があります:
| ツール | 中核的な責任 |
|---|---|
| Axios | HTTP リクエストを送信する |
| Orval | OpenAPI から API 関数と Hooks を生成 |
| TanStack Query | サーバーの状態とキャッシュを管理する |
| React | UI レンダリングとコンポーネントの状態 |
TanStack Query は実際に HTTP リクエストを送信する責任はありませんが、queryFn、mutationFn を通じて Axios およびその他のリクエスト関数を呼び出します。
1. QueryClient および QueryClientProvider
QueryClient は TanStack Query のコア オブジェクトであり、Query Cache、Mutation Cache、およびグローバル デフォルト設定を管理するために使用されます。
通常、アプリケーションの入り口で作成されます。
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
gcTime: 5 * 60_000,
retry: 2,
refetchOnWindowFocus: true,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<MainApp />
</QueryClientProvider>
);
}
コンポーネント内で使用します。
const queryClient = useQueryClient();
現在Providerが管理しているQueryClientを入手できます。
QueryClientの共通メソッド
| 方法 | 機能 |
|---|---|
invalidateQueries() | タグキャッシュの無効化 |
setQueryData() | 指定されたキャッシュを直接変更します |
getQueryData() | 同期読み取りキャッシュ |
removeQueries() | 一致するキャッシュを削除する |
resetQueries() | Queryをリセット |
refetchQueries() | 一致する Query を取得 |
cancelQueries() | マッチングリクエストをキャンセルする |
fetchQuery() | データを取得し、必要に応じてリクエストする |
prefetchQuery() | 事前にデータを取得してキャッシュする |
clear() | Query および Mutation キャッシュをクリアします |
一般に、アプリケーションのライフサイクル中は安定した QueryClient インスタンスを維持し、コンポーネントがレンダリングされるたびにインスタンスを再作成しないでください。
2. useQuery: サーバーデータの読み取り
useQuery() は主にサーバー データの取得とキャッシュに使用されます。
const result = useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
enabled: userId != null,
staleTime: 60_000,
gcTime: 300_000,
});
コアパラメータ:
| パラメータ | 機能 |
|---|---|
queryKey | 一意の識別子のクエリとキャッシュ |
queryFn | データを取得する非同期関数 |
enabled | クエリを自動的に実行するかどうか |
staleTime | データが新鮮な状態を保つ期間 |
gcTime | 未使用のキャッシュ保持時間 |
retry | リクエストが失敗した後の再試行戦略 |
select | Hook によって返されたデータを変換します |
placeholderData | 一時的に表示されるプレースホルダーデータ |
initialData | 初期キャッシュデータの設定 |
Queryステータス
const {
data,
error,
isPending,
isLoading,
isFetching,
isError,
isSuccess,
refetch,
} = useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
});
| ステータス | 意味 |
|---|---|
isPending | データはまだ正常に取得されていません |
isLoading | 最初のデータ取得が進行中です |
isFetching | 現在リクエスト中(バックグラウンド更新を含む) |
isRefetching | 初期ロードではなく再フェッチ |
isError | 現在のクエリステータスはエラーです |
isSuccess | 現在のクエリステータスは成功です |
isStale | データの有効期限が切れているか、アクティブに無効化されています |
特別な注意:
isLoading = isPending && isFetching;
したがって、enabled: false でデータがない場合、次のような現象が発生する可能性があります。
isPending = true
isLoading = false
isFetching = false
Query にはまだデータがありませんが、リクエストは行われていないため、これは矛盾しません。
代表的なコンポーネント
function UserProfile({ userId }: { userId: number }) {
const { data, isPending, isError, error, isFetching } = useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
});
if (isPending) return <div>Loading...</div>;
if (isError) return <div>{error.message}</div>;
return (
<div>
<h2>{data.username}</h2>
{isFetching && <span>Refreshing...</span>}
</div>
);
}
古いデータがすでに利用可能な場合、コンポーネントは、バックグラウンドで再要求された場合でも、キャッシュされたコンテンツを表示できます。
3. Query Key: キャッシュID
TanStack Query 異なるクエリを区別するには、配列形式 queryKey を使用します。
["user", 1][("user", 2)][("pois", { city: 1, page: 1 })][
("pois", { city: 1, page: 2 })
];
異なる Query Key は異なるキャッシュ エントリに対応します。
Query Keyレベルマッチング
たとえば:
["user"][("user", "me")][("user", "profile")][("user", "profile", 1)];
実行:
queryClient.invalidateQueries({
queryKey: ["user"],
});
デフォルトでは接頭辞一致が使用されるため、上記のすべての Query と一致します。
完全一致のみが必要な場合:
queryClient.invalidateQueries({
queryKey: ["user"],
exact: true,
});
Query Key 設計原則
クエリ結果が依存するパラメータは通常、Query Key に含める必要があります。
たとえば:
useQuery({
queryKey: ["pois", cityId, page],
queryFn: () => getPois(cityId, page),
});
Query Key に cityId が含まれていない場合、異なる都市のデータが誤って同じキャッシュを共有する可能性があります。
Hook を生成する Orval の場合、通常は自動生成された Query Key ファクトリが優先されます。
4. staleTimeとgcTime
これらは、TanStack Query の 2 つの最も重要なキャッシュ パラメーターです。
staleTime: フレッシュタイム
staleTime: 60_000;
取得または更新が成功した後、通常 60 秒間、データが fresh とみなされることを示します。
データには主に 2 つの状態があります。
Fresh
↓ staleTime 有効期限
Stale
- Fresh: データは新しいものとみなされ、コンポーネントのマウントやウィンドウのフォーカスなどのデフォルトのトリガー条件により、通常は再要求されません。
- Stale: データの有効期限が切れているため、適切なタイミングで自動的に再リクエストできます。
Stale は、キャッシュが削除されたことを意味するものではなく、また、期限切れの直後にリクエストを送信する必要があることを意味するものでもありません。
gcTime: ガベージ コレクション時間
gcTime: 300_000;
通常、Query にアクティブなサブスクライバーがいなくなった後、未使用のキャッシュが 5 分間残ることを示します。
Component Mounted
↓
Active Query
↓
Component Unmounted
↓
Inactive Query
↓
gcTime
↓
Cache Removed
まだキャッシュ内にある Query を再サブスクライブすると、再び Active 状態に入ります。
両者の違い
| コントラスト | staleTime | gcTime |
|---|---|---|
| 制御内容 | データは新しいですか? | キャッシュはいつ削除されますか? |
| メイン開始時間 | データが正常に取得または更新されました | Query が Inactive になります |
| 有効期限切れの結果 | データはStaleになります | キャッシュを再利用できる |
| すぐにリクエストするかどうか | いいえ | いいえ |
| キャッシュを削除するかどうか | いいえ | はい |
例
仮定:
staleTime: 60_000,
gcTime: 300_000,
クエリの有効期限が切れていても、コンポーネントが常にそのクエリにサブスクライブされている場合、gcTime の有効期限が切れたからといってキャッシュは削除されません。
コンポーネントがアンマウントされ、2 分後に再マウントされた場合:
- キャッシュはまだ存在します。
- データは Stale だった可能性があります。
- デフォルトでは、古いキャッシュはすぐに表示され、バックグラウンドで更新されます。
アンインストールから 5 分以上経過し、他のサブスクライバーまたは保持メカニズムがない場合:
- 元の Query はガベージ コレクションされた可能性があります。
- ページに再度アクセスするには、通常、新しいリクエストが必要です。
デフォルト値
TanStack Query v5 共通のデフォルト設定:
staleTime: 0;
gcTime: 5 * 60 * 1000;
意味:
- データのデフォルトは直ちに Stale になります。
- Inactive Query はデフォルトで 5 分間保持されます。
これは、staleTime が構成されていない場合に、ページ エントリを繰り返すとリクエストが継続的にトリガーされる理由を説明しています。
Infinity および static
staleTime: Infinity;
データは時間の経過によって自動的に古くなることはありませんが、invalidateQueries() によって無効化することはできます。
staleTime: "static";
これにはより厳密な静的データ セマンティクスがあり、通常の無効化操作では無効化をトリガーできません。
アプリケーションの実行中に実際に変更されないデータに適しています。
5. ActiveとInactive Query
Active
現在、Query Observer がサブスクライブされ有効になっています。
たとえば:
function Profile() {
const result = useUserProfileRetrieve();
// ...
}
Hook が Query をサブスクライブして有効にすると、通常は Active 状態になります。
Inactive
現在、アクティブな Query Observer はありません。
たとえば、ユーザーが Profile ページを離れると、関連コンポーネントはアンインストールされ、他のコンポーネントはそのコンポーネントをサブスクライブしません。
ただし、Query はキャッシュ内にまだ存在する可能性があります。
Query ライフサイクル
useQuery Mount
↓
Active
↓
Fetch / Cache
↓
Component Unmount
↓
Inactive
↓
gcTime Expires
↓
Garbage Collection
注: active/inactive はサブスクリプションのステータスを示します。 fresh/stale はデータの鮮度を示します。それらは 2 つの独立した次元です。
6. 自動再リクエスト(Refetch)
TanStack Query は、デフォルトで特定の時間に Stale データを再要求します。
| トリガー条件 | 構成 |
|---|---|
| コンポーネントの再マウント | refetchOnMount |
| ブラウザウィンドウのフォーカスが再設定されました | refetchOnWindowFocus |
| ネットワークの再接続 | refetchOnReconnect |
| 定期的なポーリング | refetchInterval |
| 手動で再リクエストする | refetch() |
| キャッシュの無効化 | invalidateQueries() |
たとえば:
useQuery({
queryKey: ["user", "profile"],
queryFn: getProfile,
staleTime: 60_000,
refetchOnWindowFocus: false,
refetchOnReconnect: true,
refetchOnMount: true,
});
refetchInterval
refetchInterval: 30_000;
約 30 秒ごとにデータの再取得を試みることを示します。
スケジュールされたポーリングは staleTime によって完全に制限されているわけではなく、独立した自動要求メカニズムであることに注意してください。
enabled
useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId!),
enabled: userId != null,
});
これは、有効な userId を取得した後にのみリクエストが自動的に送信されることを意味します。
無効な状態は自動無効化と再フェッチの動作にも影響するため、単にリクエストを遅らせるものとして解釈しないでください。
7. useMutation: サーバーステータスの変更
useMutation() は通常、データの作成、更新、または削除に使用されます。
const mutation = useMutation({
mutationFn: updateProfile,
});
実行:
mutation.mutate({
username: "Tom",
});
Mutationステータス
| ステータス | 意味 |
|---|---|
isIdle | まだ実行されていません |
isPending | 実行中 |
isSuccess | 成功 |
isError | 失敗しました |
data | データが正常に返されました |
error | エラーオブジェクト |
mutate および mutateAsync
mutation.mutate(data);
Mutation をトリガーし、コールバックを通じて結果を処理します。
await mutation.mutateAsync(data);
Promise を返します。これを try/catch と組み合わせて結果を処理できます。
try {
await mutation.mutateAsync({
username: "Tom",
});
} catch (error) {
console.error(error);
}
Mutation は、Query のように、デフォルトでは Query Key 単位で長期サーバー データ キャッシュを自動的に維持しません。
8. Mutation Lifecycle
Mutation は 4 つの重要なライフサイクル コールバックを提供します。
| コールバック | 通話時間 |
|---|---|
onMutate | 実行前 mutationFn |
onSuccess | リクエストは成功しました |
onError | リクエストが失敗しました |
onSettled | リクエストが成功または失敗した後 |
onSuccess
const mutation = useMutation({
mutationFn: updateProfile,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["user", "profile"],
});
},
});
書き込みが成功した後に関連するキャッシュをフラッシュするのに適しています。
onError
onError: (error) => {
console.error(error);
};
失敗の処理、エラーの表示、またはオプティミスティックな更新のロールバックに使用されます。
onSettled
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: ["user", "profile"],
});
};
成功しても失敗しても、最終的には実行されます。
コールバックが Promise を返した場合、TanStack Query は対応する Mutation ライフサイクル処理を終了する前に、コールバックが完了するまで待機します。
注: 失敗時にデータを更新する必要がない場合は、常に onSettled でサーバーに要求する必要はありません。
9. 楽観的なアップデート (Optimistic Update)
楽観的な更新とは次のことを指します。
サーバーが成功を確認する前に、クライアント インターフェイスまたはキャッシュを更新します。
たとえば、ユーザーがニックネームを変更するとします。
User Changes Name
↓
Update UI Immediately
↓
Send PATCH Request
│
├── Success → Keep / Synchronize
│
└── Error → Rollback
完全な例
const queryClient = useQueryClient();
const profileKey = ["user", "profile"] as const;
const mutation = useMutation({
mutationFn: (newName: string) => updateProfile({ username: newName }),
onMutate: async (newName) => {
await queryClient.cancelQueries({
queryKey: profileKey,
});
const oldProfile = queryClient.getQueryData<{
username: string;
}>(profileKey);
queryClient.setQueryData(profileKey, (old: typeof oldProfile) =>
old ? { ...old, username: newName } : old,
);
return { oldProfile };
},
onError: (_error, _variables, onMutateResult) => {
if (onMutateResult?.oldProfile !== undefined) {
queryClient.setQueryData(profileKey, onMutateResult.oldProfile);
}
},
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: profileKey,
});
},
});
3 つの重要なステップ:
cancelQueries(): 古いリクエストが返された後にオプティミスティック更新を上書きしないようにします。setQueryData(): クライアント キャッシュを直ちに変更します。onError(): リクエストが失敗した場合に以前のデータを復元します。
onMutate() の戻り値を後続のコールバックに渡して、古いキャッシュ データを保存できます。
注: 複雑な同時実行性 Mutation は更新順序の競合を引き起こす可能性があるため、楽観的な更新がすべてのビジネスに適しているとは限りません。
さらに、TanStack Query v5 では、useMutation() で onMutate を構成する必要があります。 mutate() のコールレベルのコールバックには、主に onSuccess、onError、onSettled が含まれます。
10. invalidateQueries: キャッシュの無効化
queryClient.invalidateQueries({
queryKey: ["user", "profile"],
});
機能:
- 指定された Query と一致します。
- Stale というラベルを付けます。
- デフォルトでは、条件を満たす Active Query に対してバックグラウンド再取得がトリガーされます。
Server Data Updated
↓
invalidateQueries()
↓
Cache Marked Stale
│
├── Active → Refetch
│
└── Inactive → Wait for Future Usage
障害の範囲
queryClient.invalidateQueries({
queryKey: ["user"],
});
["user"] というプレフィックスが付いた複数の Query と一致できます。
完全一致:
queryClient.invalidateQueries({
queryKey: ["user", "profile"],
exact: true,
});
refetchType
queryClient.invalidateQueries({
queryKey: ["user"],
refetchType: "all",
});
一般的なオプション:
active: デフォルト。アクティブな Query に一致する再リクエスト。inactive: 非アクティブな Query に一致するリクエスト。all:一致するアクティブおよび非アクティブな Query をすべて再取得します。none: 失敗をマークするだけで、再リクエストはトリガーされません。
特別な静的構成を使用して無効化された Query および Query には追加の制限があります。
11. setQueryData: キャッシュを直接変更する
setQueryData() は HTTP リクエストを送信せず、Query Cache を直接変更します。
queryClient.setQueryData(["user", "profile"], {
username: "Bob",
avatar: "avatar.jpg",
});
Query が存在しない場合、有効なデータが提供されたときにキャッシュ エントリが作成される可能性があります。
成功後に Mutation がキャッシュに書き込まれます
const mutation = useMutation({
mutationFn: updateProfile,
onSuccess: (updatedProfile) => {
queryClient.setQueryData(["user", "profile"], updatedProfile);
},
});
これにより、サーバーが完全で信頼できる最新のデータを返す場合、追加の GET リクエストを減らすことができます。
機能アップデート
queryClient.setQueryData(
["user", "profile"],
(old: UserProfile | undefined) => {
if (!old) return old;
return {
...old,
username: "Bob",
};
},
);
キャッシュ内の古いオブジェクトを直接変更しないでください。
old.username = "Bob";
return old;
新しいオブジェクトを作成するには、不変の更新を使用する必要があります。
12. getQueryData、removeQueries、resetQueries
getQueryData
既存のキャッシュを同期的に読み取ります。
const profile = queryClient.getQueryData<UserProfile>(["user", "profile"]);
HTTP リクエストは積極的に送信されません。
removeQueries
queryClient.removeQueries({
queryKey: ["user", "profile"],
});
一致するキャッシュ レコードを削除します。
不要になったデータをクリーンアップするのに適していますが、コンポーネントのサブスクリプションがまだある Query の場合、削除後のコンポーネントのステータスと後続のリクエストを考慮する必要があります。
resetQueries
queryClient.resetQueries({
queryKey: ["user", "profile"],
});
マッチ Query を元の状態に戻します。
initialData で Query を設定すると、この初期データに戻すことが可能です。
通常、アクティブな Query も再リクエストされます。
clear
queryClient.clear();
Query Cache と Mutation Cache をクリーンアップします。
これは、クライアント サーバーのステータスを全体としてクリアする必要があるが、まだ必要なデータを無差別にクリアすることは避けるべきシナリオに適しています。
コントラスト
| 方法 | キャッシュの削除 | リクエスト |
|---|---|---|
invalidateQueries | いいえ | デフォルトでは、条件を満たすActive Queryが更新されます。 |
setQueryData | いいえ | いいえ |
removeQueries | はい | 更新を自動的に開始しない |
resetQueries | リセット状態 | Active Query は通常更新されます |
clear | キャッシュをクリアする | 更新を自動的に開始しない |
13. 自動キャッシュとリクエストの重複排除
2 つのコンポーネントが同時に同じデータを要求した場合:
function Header() {
useUserProfileRetrieve();
}
function ProfilePage() {
useUserProfileRetrieve();
}
同じ QueryClient と Query Key を使用する限り:
Component A ──┐
├── Shared Query Cache
Component B ──┘
↓
Single Query
↓
HTTP Request
TanStack Query は通常、同じリクエストの 2 つのコピーを送信するのではなく、同じ実行中のリクエストを共有します。
ただし、注意してください:
- 異なる Query Key では、重複は自動的に削除されません。
- リクエスト完了後に再度リクエストするかどうかは、鮮度と設定によって異なります。
- 通常のメモリ キャッシュは、2 つの異なる QueryClient 間で共有されません。
14. Retry: 失敗後の再試行
TanStack Query は、クライアント環境で失敗した Query をデフォルトで再試行します。共通のデフォルト回数は 3 回です。
retry: 3;
オフにすることができます:
retry: false;
エラーの種類に基づいて判断することもできます。
retry: (failureCount, error) => {
const status = axios.isAxiosError(error) ? error.response?.status : undefined;
if (status === 400 || status === 401 || status === 403) {
return false;
}
return failureCount < 2;
};
一般原則:
- 一時的なネットワーク障害: もう一度試してください。
- サーバーは一時的に利用できません。再試行することを検討してください。
- パラメータ エラー: 通常、再試行する必要はありません。
- 認証または権限エラー: 通常、再試行は必要ありません。
useMutation はデフォルトでは自動的に再試行しないため、Query の再試行ポリシーを書き込み操作に無差別に使用しないでください。
非冪等書き込みリクエストを自動的に再試行する場合は、特に注意が必要です。
15. selectのQuery
select は、Hook によって返されたデータを変換するために使用されます。
たとえば、バックエンドは次を返します。
{
id: 1,
username: "Alice",
email: "alice@example.com"
}
ユーザー名だけを抽出することもできます。
const { data: username } = useQuery({
queryKey: ["user", "profile"],
queryFn: getProfile,
select: (data) => data.username,
});
注:
selectは、この Query Observer によって返されるデータに影響します。- Query Cache の元のデータは、選択した値に直接置き換えられません。
複数のコンポーネントが同じサーバー データから異なるフィールドを読み取る状況に適しています。
16. initialDataとplaceholderData
initialData
useQuery({
queryKey: ["user", 1],
queryFn: getUser,
initialData: {
id: 1,
username: "Alice",
},
});
initialDataはQuery Cacheに初期値を入れます。
信頼できる初期データがすでに存在する状況に適しています。
placeholderData
useQuery({
queryKey: ["user", 1],
queryFn: getUser,
placeholderData: {
id: 1,
username: "Loading...",
},
});
placeholderData は一時的な表示にのみ使用され、正式なクエリ結果として Query Cache に書き込まれることはありません。
違い
| 特長 | initialData | placeholderData |
|---|---|---|
| Query Cacheと書く | はい | いいえ |
| 実際の初期データとして | はい | いいえ |
| 一時的な UI プレースホルダに適しています | 一般的には優先事項ではありません | はい |
| 鮮度判定に影響はありますか? | はい | 実際のデータとしてキャッシュされない |
17. ページングと keepPreviousData
API のページングの例:
GET /api/v1/pois?page=1
GET /api/v1/pois?page=2
ページングの切り替え時に古いデータがすぐに破棄されると、インターフェイスが一時的に空白になることがあります。
TanStack Query v5 は、placeholderData および keepPreviousData と一緒に使用できます。
import { keepPreviousData, useQuery } from "@tanstack/react-query";
const result = useQuery({
queryKey: ["pois", page],
queryFn: () => getPois(page),
placeholderData: keepPreviousData,
});
機能:
Page 1 Data
↓
User Switches to Page 2
↓
Keep Showing Page 1 Temporarily
↓
Fetch Page 2
↓
Display Page 2
isPlaceholderData を使用すると、現在表示されているデータがプレースホルダー データであるかどうかを判断できます。
これは通常のリストのページネーションに最適です。
18. useInfiniteQuery: 無限スクロール
無限スクロール、さらなる読み込みなどのシナリオでは、useInfiniteQuery を使用できます。
const query = useInfiniteQuery({
queryKey: ["pois", "infinite"],
queryFn: ({ pageParam }) => getPois({ cursor: pageParam }),
initialPageParam: null as string | null,
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
});
中心となる概念:
| プロパティ | 機能 |
|---|---|
pages | 読み込まれたページのリスト |
pageParams | 各ページに使用されるページネーションパラメータ |
fetchNextPage() | 次のページを取得 |
hasNextPage | 次のページはありますか? |
isFetchingNextPage | 次のページに進みますか? |
getNextPageParam | 次のページのパラメータを計算する |
getNextPageParam から null または undefined が返されるのは、通常、次のページがないことを意味します。
Map POI の大きなデータ リストや検索結果の場合、無限スクロールが考慮される場合がありますが、すべてのインターフェイスで必ずしも必要というわけではありません。
19. AbortSignal: キャンセルのリクエスト
TanStack Query は AbortSignal を Query Function に提供します。
useQuery({
queryKey: ["user", userId],
queryFn: ({ signal }) =>
axios.get(`/api/v1/users/${userId}`, {
signal,
}),
});
リクエストがキャンセルされた場合、Axios は Signal を消費してクライアントの HTTP リクエストを終了できます。
一般的なシナリオ:
- ユーザーはクエリ条件を素早く切り替えることができます。
- ページが離れます。
- 手動でキャンセルしてください。
- 古いリクエストはもう必要ありません。
注:
signalの存在は、すべての非アクティブなリクエストが自動的に中止されることを意味するものではありません。- リクエスト関数は実際に Signal を使用する必要があります。
- クライアントリクエストを中止しても、バックエンドが実行を停止したことは保証されません。
Orval によって生成された API 関数は、通常、TanStack Query と組み合わせて Signal を渡すことができます。
20. Prefetch: 事前にデータを取得する
prefetchQuery() は事前にキャッシュを埋めることができます。
たとえば、ユーザーが詳細ページを開こうとしているとします。
await queryClient.prefetchQuery({
queryKey: ["poi", poiId],
queryFn: () => getPoi(poiId),
staleTime: 60_000,
});
次に、コンポーネントが実行されます。
useQuery({
queryKey: ["poi", poiId],
queryFn: () => getPoi(poiId),
staleTime: 60_000,
});
キャッシュがまだ新しい場合は、通常、事前に取得したデータを再度リクエストすることなく使用できます。
一般的なトリガー方法:
- マウスHover。
- リスト項目が表示領域に入ります。
- ルートナビゲーション前。
- ページのプリロード。
注: プリロードはネットワーク リソースも消費するため、すべてのページのデータを無差別にプリロードしないでください。
21. Structural Sharing
TanStack Query のデフォルトでは、古いデータと新しいデータの変更されていない参照を可能な限り再利用します。
たとえば、サーバーから返された一部のフィールドは変更されていません。
{
user: {
id: 1,
username: "Alice"
},
total: 10
}
新しい応答が古いデータと同じである場合、TanStack Query は関連するオブジェクト参照を再利用して、不必要な参照の変更を減らすことができます。
これをStructural Sharingといいます。
主に JSON 互換のデータ構造で動作します。
JSON 以外の特殊なクラスまたは型のインスタンスを含むデータについては、制限を考慮する必要があります。
通常は、デフォルト設定をそのまま使用します。
22. Query CacheとMutation Cache
TanStack Query 内には 2 つの主要なキャッシュ管理構造があります。
Query Cache
読み取り操作のデータとステータスを管理します。
QueryCache
├── ["user", "profile"]
├── ["pois", { city: 1 }]
└── ["itinerary", 123]
担当者:
- データの再利用。
- 鮮度管理。
- クエリのライフサイクル。
- バックグラウンドでの再リクエスト。
- キャッシュのクリーニング。
Mutation Cache
Mutationの実行状況や履歴を管理します。
MutationCache
├── Update Profile
├── Delete POI
└── Create Itinerary
各 Mutation の戻り結果を関連する Query Cache に自動的に同期することはありません。
Mutation が成功した後、多くの場合、積極的に呼び出す必要があるのはこのためです。
queryClient.invalidateQueries(...)
または:
queryClient.setQueryData(...)
23. グローバルコンフィグレーションとOrvalの関係
Orval を使用するプロジェクトでは、通常、生成された Query Hook を自分で再実装する必要はありません。
たとえば、Orval が生成されます。
useUserProfileRetrieve();
直接使用できます。
const { data, isPending } = useUserProfileRetrieve({
query: {
staleTime: 60_000,
enabled: true,
},
});
特定のコール署名は、Orval で生成されたコードの影響を受けます。
統合された構成が必要な場合:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
gcTime: 300_000,
retry: 2,
refetchOnWindowFocus: false,
},
mutations: {
retry: 0,
},
},
});
プロジェクトでは一般的に次のことが推奨されます。
- グローバル設定は、ほとんどのインターフェイスのデフォルトの動作に適しています。
- 特殊なインターフェイスは Hook Options でカバーされます。
- フェイルオーバー操作には Orval Query Key ファクトリを使用します。
- Generated Code を直接変更しないでください。
24. よくある質問
Q1: TanStack Query を使用した後も、React State は必要ですか?
必要。
TanStack Query は主にサーバーの状態を管理します。
React useState は現在も次の用途に使用されています。
- Modal が開いているかどうか。
- フォーム入力。
- 現在Tab。
- ローカル UI インタラクション。
- ユーザーが編集した、まだ送信されていないデータ。
React State をすべて TanStack Query に入れることはお勧めできません。
Q2: staleTime の有効期限が切れたらすぐにリクエストが送信されますか?
通常はそうではありません。
データを Stale にするだけです。その後、コンポーネントがマウントされたとき、ウィンドウがフォーカスされたとき、ネットワークが再接続されたとき、またはその他の設定されたタイミングが発生したときに、再取得されます。
Q3: gcTime は有効期限が切れたら再リクエストされますか?
しません。
これはガベージ コレクションを担当し、再フェッチは行いません。
キャッシュが削除されている場合、その後の Query への再サブスクリプションには通常、新しいリクエストが必要になります。
Q4: 2 つのコンポーネントが同じ Hook を呼び出す場合、2 つのリクエストが送信されますか?
同じ QueryClient と同じ Query Key に対する同時リクエストの場合、通常、リクエストは再利用されます。
ただし、リクエストが終了し、キャッシュが Stale にある場合は、後続のマウントによって再フェッチがトリガーされる可能性があります。
Q5: Mutation が成功すると、GET キャッシュは自動的に更新されますか?
通常はそうではありません。
プロジェクトが自動無効化ポリシーで追加構成されていない限り、キャッシュの一貫性は invalidateQueries() または setQueryData() を通じて手動で維持する必要があります。
Q6: invalidateQueries をいつ使用するか、setQueryData をいつ使用するか?
- 最新データはサーバー
invalidateQueries()から再確認する必要があります。 - Mutation は完全な最新データを返しました:
setQueryData()が利用可能です。 - 複数の関連クエリを同時に更新する必要があるため、包括的なキャッシュ戦略を設計する必要があります。
概要
TanStack Query の中心となるコンセプトは 4 つの部分に分けることができます。
| 分類 | コアコンセプト |
|---|---|
| データ読み取り | useQuery、Query Key、QueryFn、Query ステータス |
| キャッシュメカニズム | staleTime、gcTime、Active/Inactive、Structural Sharing |
| データの変更 | useMutation、Lifecycle、Optimistic Update |
| キャッシュ制御 | invalidateQueries、setQueryData、resetQueries、Prefetch |
コアデータプロセス:
React Component
↓
useQuery / useMutation
↓
QueryClient
│
├── QueryCache
└── MutationCache
↓
queryFn / mutationFn
↓
Axios
↓
Django API
最もマスターする必要があるもの:
- Query Key キャッシュを一意に識別する方法。
staleTimeとgcTimeの違い。- Active / Inactive と Fresh / Stale の関係。
- Query データを自動再取得する方法。
- Mutation 成功後にキャッシュを更新または無効にする方法。
onMutate、onError、および楽観的なアップデート。- Orvalで生成したHooksからQueryClientを再利用する方法。