← 编程返回顶部

TanStack Query:React 服务器状态管理

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管理服务器状态和缓存
ReactUI 渲染和组件状态

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 状态。

两者区别

对比staleTimegcTime
控制内容数据是否新鲜缓存何时删除
主要开始时机数据成功获取或更新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 提供四个重要生命周期回调。

回调调用时机
onMutatemutationFn 执行之前
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,
    });
  },
});

三个关键步骤:

  1. cancelQueries():避免旧请求返回后覆盖乐观更新。
  2. setQueryData():立即修改客户端缓存。
  3. onError():请求失败时恢复之前的数据。

onMutate() 的返回值可以传递给后续回调,用于保存旧缓存数据。

注意:复杂的并发 Mutation 可能出现更新顺序冲突,因此乐观更新不一定适用于所有业务。

另外,在 TanStack Query v5 中,onMutate 需要配置在 useMutation() 中;mutate() 的调用级回调主要包括 onSuccess、onError 和 onSettled。

10. invalidateQueries:缓存失效

queryClient.invalidateQueries({
  queryKey: ["user", "profile"],
});

作用:

  1. 匹配指定的 Query。
  2. 将它们标记为 Stale。
  3. 默认对符合条件的 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。

区别

特性initialDataplaceholderData
写入 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

最需要掌握的内容:

  1. Query Key 如何唯一标识缓存。
  2. staleTime 与 gcTime 的区别。
  3. Active / Inactive 与 Fresh / Stale 的关系。
  4. Query 如何自动重新获取数据。
  5. Mutation 成功后如何更新或失效缓存。
  6. onMutate、onError 与乐观更新。
  7. Orval 生成的 Hooks 如何复用 QueryClient。

参考文档