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:缓存标识
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,不同城市的数据可能错误地共用同一个缓存。
对于 Orval 生成的 Hook,通常优先使用自动生成的 Query Key 工厂。
4. staleTime 与 gcTime
这是 TanStack Query 最重要的两个缓存参数。
staleTime:新鲜时间
staleTime: 60_000;
表示数据在成功获取或更新后,通常有 60 秒被视为 fresh。
数据存在两种主要状态:
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 描述的是数据新鲜程度,它们是两个独立维度。
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 提供四个重要生命周期回调。
| 回调 | 调用时机 |
|---|---|
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,
});
},
});
三个关键步骤:
cancelQueries():避免旧请求返回后覆盖乐观更新。setQueryData():立即修改客户端缓存。onError():请求失败时恢复之前的数据。
onMutate() 的返回值可以传递给后续回调,用于保存旧缓存数据。
注意:复杂的并发 Mutation 可能出现更新顺序冲突,因此乐观更新不一定适用于所有业务。
另外,在 TanStack Query v5 中,onMutate 需要配置在 useMutation() 中;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 恢复到初始状态。
如果 Query 配置了 initialData,可能恢复该初始数据。
活跃 Query 通常还会重新请求。
clear
queryClient.clear();
清理 Query Cache 和 Mutation Cache。
适用于需要整体清除客户端服务器状态的场景,但应避免无差别清除仍然需要的数据。
对比
| 方法 | 删除缓存 | 重新请求 |
|---|---|---|
invalidateQueries | 否 | 默认刷新符合条件的 Active Query |
setQueryData | 否 | 否 |
removeQueries | 是 | 不自动发起刷新 |
resetQueries | 重置状态 | Active Query 通常会刷新 |
clear | 清空缓存 | 不自动发起刷新 |
13. 自动缓存与请求去重
如果两个组件同时请求同一份数据:
function Header() {
useUserProfileRetrieve();
}
function ProfilePage() {
useUserProfileRetrieve();
}
只要它们使用相同的 QueryClient 和 Query Key:
Component A ──┐
├── Shared Query Cache
Component B ──┘
↓
Single Query
↓
HTTP Request
TanStack Query 通常会共享同一个进行中的请求,而不是重复发送两份相同请求。
但注意:
- 不同 Query Key 不会自动去重。
- 请求完成后是否再次请求取决于新鲜度及配置。
- 两个不同 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. Query 的 select
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 会向 Query Function 提供 AbortSignal。
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 内部主要有两种缓存管理结构。
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:两个组件调用同一个 Hook,会发送两个请求吗?
对于相同 QueryClient、相同 Query Key 的同时进行中的请求,通常会复用请求。
但如果请求已结束并且缓存处于 Stale,后续挂载仍可能触发重新获取。
Q5:Mutation 成功后是否会自动更新 GET 缓存?
通常不会。
需要手动通过 invalidateQueries() 或 setQueryData() 维护缓存一致性,除非项目额外配置了自动失效策略。
Q6:什么时候用 invalidateQueries,什么时候用 setQueryData?
- 最新数据需要重新从服务器确认:
invalidateQueries()。 - Mutation 已返回完整最新数据:可以使用
setQueryData()。 - 多个相关查询需要同步更新:需要综合设计缓存策略。
总结
TanStack Query 的核心概念可以分为四部分:
| 分类 | 核心概念 |
|---|---|
| 数据读取 | 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。