TanStack Query
面向 Web 应用的异步服务端状态管理工具,统一处理请求缓存、同步、失效、Mutation 和后台更新。
项目概述
TanStack Query(原 React Query)不绑定 REST、GraphQL 或特定请求库。它围绕 Query Key 管理远程数据生命周期,并为 React、Vue、Solid、Svelte、Preact、Lit 等框架提供适配器。
TanStack Query 是采用 MIT 许可证的异步服务端状态管理库,原名 React Query。它把请求得到的数据视为具有新鲜度、缓存、重试、失效和同步生命周期的 Server State,而不是让开发者手动把 Loading、Error、Data 和时间戳塞进全局 Store。核心 Query Cache 与框架无关,官方为 React、Preact、Vue、Solid、Svelte、Lit 和实验性 Angular 适配器提供 Hooks 或响应式 API;请求函数本身可以使用 fetch、Axios、GraphQL Client 或任意返回 Promise 的实现。
主要特点
TanStack Query 把远程数据的获取、缓存、刷新和写入协调成可观察的状态模型。
协议无关的 Query Function
只要返回 Promise,就可以接入 fetch、Axios、GraphQL、RPC、IndexedDB 或其他异步数据源。
Query Key 缓存模型
序列化 Query Key 标识数据身份,参数成为 Key 的一部分后,缓存、去重、失效和预取都能精确工作。
Stale-While-Revalidate
先显示缓存数据,再按 staleTime、挂载、窗口聚焦、网络重连或轮询策略在后台重新验证。
请求去重与取消
相同 Query 可共享进行中的 Promise,Query Function 还能使用 AbortSignal 在失效或卸载时取消网络工作。
Mutation 与失效
useMutation 表达创建、更新和删除操作,成功后可精确 invalidateQueries、setQueryData 或预取相关数据。
乐观更新
onMutate 可暂停相关查询、保存旧缓存并提前写入界面,失败时回滚,完成后再与服务端结果同步。
分页与无限查询
useQuery 支持分页 Key 与保留上一页数据,useInfiniteQuery 管理 Cursor、页面参数和增量加载。
并行与依赖查询
多个 Query 可并行执行,enabled 和 useQueries 能表达依赖条件、动态查询集合和复杂数据组合。
结构共享与渲染优化
JSON 兼容数据默认进行 Structural Sharing,并只跟踪组件实际读取的结果属性,减少无关重渲染。
SSR、Hydration 与 Streaming
可在服务端 Prefetch 后 Dehydrate Cache,再通过 HydrationBoundary 交给客户端,支持 Next.js 等框架。
离线与持久化
Network Mode、Persist Query Client 和 Storage Persister 可支持离线读取、暂停 Mutation 与缓存恢复。
Devtools 与 ESLint
官方 Devtools 展示 Query、Observer 和缓存状态,ESLint Plugin 则检查不稳定依赖和对象 Rest 等常见问题。
适用场景
当多个页面共享远程数据、需要后台刷新或 Mutation 后保持一致时,它能显著减少手写状态代码。
SaaS 与管理后台
表格、详情、筛选、编辑和批量操作共享服务端数据,Mutation 后可按业务边界精确失效缓存。
搜索与筛选页面
把关键词、分页和筛选条件放入 Query Key,自动保留各组合缓存并取消过期请求。
无限信息流
useInfiniteQuery 管理 Cursor 与页面集合,可结合虚拟列表实现 Feed、日志和活动记录。
实时与定时刷新界面
轮询、窗口聚焦重取和手动 Cache Update 可用于监控、订单状态和协作数据;实时推送也能写入 Query Cache。
SSR 和全栈 React
路由 Loader 或 Server Component 可以 Prefetch,再通过 Dehydrate/Hydrate 缩短客户端数据瀑布。
多框架产品
共享 Query Core 与业务 Key 约定,同时在 React、Vue、Svelte、Solid 等应用中使用对应官方适配器。
弱网和离线应用
持久化 Cache、Network Mode 与 Mutation 恢复能力可以为 PWA 和移动 Web 提供更连续的数据体验。
优点与注意事项
技术选型不仅要看能力,也要理解它带来的团队成本。
主要优点
TanStack Query 擅长的地方
- 把缓存、新鲜度、重试和后台同步从业务组件中抽离
- 协议无关,可继续使用现有 REST、GraphQL 或 RPC Client
- Query Key 为去重、失效、预取和参数化缓存提供统一标识
- Mutation、乐观更新与精确失效能维护读写后的界面一致性
- 分页、Infinite Query、Suspense、SSR 和离线场景覆盖完整
- 结构共享与属性跟踪减少许多不必要的 React 重渲染
- 官方支持多种前端框架,核心概念可跨技术栈复用
- Devtools 和成熟文档便于观察缓存状态及排查重复请求
需要注意
采用前应考虑的问题
Modal 开关、表单草稿、主题和本地工作流通常应留在组件状态或专用 Store,不能因为 Query Cache 可写就全部放进去。
staleTime 默认是 0,已缓存数据会被视为 Stale,并可能在挂载、聚焦或重连时后台刷新。应按业务容忍度显式设置。
Query Function 使用的分页、筛选、用户或租户参数应进入 Key;遗漏会串缓存,使用不稳定对象则增加维护难度。
每次 Mutation 后失效全部 Query 会增加流量和界面抖动,完全手写 Cache 又容易遗漏。应定义资源层级和 Key Factory。
Query Function 必须检查 response.ok 并抛出 Error,否则失败响应可能被当成成功数据进入 Cache。
默认失败 Query 会重试。权限错误、参数错误、昂贵请求和服务端限流应根据错误类型调整 retry 与 backoff。
并发 Mutation、服务端重新排序和权限拒绝会让简单追加失效,需要保存旧值、取消 Query 并在失败后恢复。
服务端应按请求创建 Cache,避免不同用户数据泄漏;序列化 Hydrated State 时还必须正确转义以防 XSS。
把 useQuery 放在深层 Client Component 才开始请求会延迟内容。应结合路由 Loader、Prefetch 和并行请求规划数据边界。
大量高基数 Key、无限页面或大响应需要合理 gcTime、maxPages 和清理策略,服务端尤其不能无界保留。
React、Vue、Solid 和 Svelte 包的版本线与 API 发布节奏可能不同,跨框架文档和示例不能机械复制。
快速开始
下面以 React 适配器为例,配置 QueryClient、查询项目、执行新增 Mutation,并加入分页和 SSR Hydration。
bashnpm install @tanstack/react-query
npm install --save-dev @tanstack/react-query-devtoolstsximport { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
retry: 2,
},
},
});
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}typescriptexport type Project = {
id: string;
name: string;
homepage: string;
};
export async function getProjects(signal?: AbortSignal): Promise<Project[]> {
const response = await fetch("/api/projects", { signal });
if (!response.ok) {
throw new Error(`加载失败:${response.status}`);
}
return response.json();
}tsximport { useQuery } from "@tanstack/react-query";
import { getProjects } from "./api";
export function ProjectList() {
const projectsQuery = useQuery({
queryKey: ["projects"],
queryFn: ({ signal }) => getProjects(signal),
staleTime: 2 * 60 * 1000,
});
if (projectsQuery.isPending) return <p>正在加载…</p>;
if (projectsQuery.isError) {
return <p role="alert">{projectsQuery.error.message}</p>;
}
return (
<ul>
{projectsQuery.data.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
);
}tsximport { useMutation, useQueryClient } from "@tanstack/react-query";
import { createProject } from "./api";
export function AddProjectButton() {
const queryClient = useQueryClient();
const createMutation = useMutation({
mutationFn: createProject,
onSuccess: () =>
queryClient.invalidateQueries({
queryKey: ["projects"],
}),
});
return (
<button
disabled={createMutation.isPending}
onClick={() => createMutation.mutate({ name: "New project" })}
>
{createMutation.isPending ? "正在添加…" : "添加项目"}
</button>
);
}typescriptconst createMutation = useMutation({
mutationFn: createProject,
onMutate: async (draft) => {
await queryClient.cancelQueries({ queryKey: ["projects"] });
const previous = queryClient.getQueryData<Project[]>(["projects"]);
queryClient.setQueryData<Project[]>(["projects"], (current = []) => [
...current,
{ id: "optimistic", ...draft },
]);
return { previous };
},
onError: (_error, _draft, context) => {
queryClient.setQueryData(["projects"], context?.previous);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["projects"] });
},
});tsximport {
keepPreviousData,
useQuery,
} from "@tanstack/react-query";
export function ProjectPage({ page }: { page: number }) {
const query = useQuery({
queryKey: ["projects", { page }],
queryFn: ({ signal }) => getProjectPage(page, signal),
placeholderData: keepPreviousData,
});
return (
<>
<ProjectGrid projects={query.data?.items ?? []} />
{query.isFetching ? <span>正在更新…</span> : null}
</>
);
}tsximport {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";
export default async function ProjectsPage() {
const queryClient = new QueryClient();
await queryClient.prefetchQuery({
queryKey: ["projects"],
queryFn: () => getProjects(),
});
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProjectList />
</HydrationBoundary>
);
}bashnpm run dev
# 发布前执行项目自己的检查
npm run typecheck
npm run build下一步:先设计稳定的 Query Key,再为每类数据明确 staleTime、错误策略和失效边界。不要把所有客户端 UI 状态放进 Query Cache,也不要默认接受每次聚焦窗口就重新请求的行为。
类似项目
这些站内框架都有官方 TanStack Query 适配器或常见集成;SWR、Apollo Client 和 RTK Query 代表其他服务端状态方案。
React
用于构建 Web 和原生用户界面的组件化 JavaScript 库。
查看项目Vue.js
渐进式 JavaScript 框架,易学易用且拥有优秀的性能表现。
查看项目Svelte
将组件编译为精简 JavaScript 的现代 Web UI 框架。
查看项目SolidJS
采用细粒度响应式和 JSX 构建高性能 Web 用户界面的 JavaScript 库。
查看项目Next.js
基于 React 的全栈 Web 框架,覆盖渲染、路由和部署。
查看项目TanStack Router
面向 React 与 Solid 的类型安全路由器,覆盖文件路由、Search Params、数据加载、预加载和代码分割。
查看项目SWR
Vercel 维护的 React 数据请求 Hook,以轻量 Stale-While-Revalidate 缓存模型著称。
访问官网Apollo Client
面向 GraphQL 的完整客户端,提供规范化缓存、本地状态、分页和 Devtools。
访问官网RTK Query
Redux Toolkit 内置的数据请求与缓存方案,适合已经采用 Redux Store 的应用。
访问官网TanStack Query vs SWR
TanStack Query 与 SWR 都以 Stale-While-Revalidate 改善 React 数据获取,但 TanStack Query 提供更完整的 Query 生命周期、Mutation、分页、失效和多框架能力。
| 比较维度 | TanStack Query | SWR |
|---|---|---|
| 核心抽象 | QueryClient、Query Cache、Observer 与 Query Key | 以 Key 和 Fetcher 为中心的轻量 React Hook |
| 框架范围 | React、Vue、Solid、Svelte、Preact、Lit 等 | 主要面向 React |
| Mutation | useMutation、生命周期、失效和乐观更新 | 通过 mutate、useSWRMutation 与 Cache 更新实现 |
| 分页与无限加载 | useQuery 分页与 useInfiniteQuery 专用模型 | useSWRInfinite 管理页面 Key 和集合 |
| 默认策略 | 数据默认 Stale、失败重试三次、非活跃 Cache 五分钟回收 | 自动聚焦重验证与轻量缓存默认值 |
| 渲染优化 | 结构共享、属性跟踪与 select | 依赖比较和 Hook 订阅优化 |
| Devtools | 官方 Query Cache Devtools 与 ESLint Plugin | 生态工具较轻量 |
| 学习与配置 | 概念和选项更多,适合复杂数据流 | API 较小,简单请求更快上手 |
| 更适合 | 复杂读写、分页、离线、SSR 和多框架产品 | React 中轻量读取、简单刷新和较少 Mutation |
如果应用以少量读取请求为主,希望用极简 Hook 获得缓存与重新验证,SWR 往往足够;如果存在复杂 Mutation、乐观更新、Infinite Query、精细失效、离线或跨框架需求,TanStack Query 的完整状态模型更合适。无论选择哪一个,都应先定义服务端数据的新鲜度、错误策略和 Query Key。
资料核验
版本、维护信息与本页采用的官方资料来源。
本页依据 TanStack Query 官方 React Quick Start、Important Defaults、Caching、Mutation、Render Optimizations、SSR 与框架支持文档,以及 npm 和官方源码仓库整理。版本指 React 适配器;Vue、Svelte、Solid 等适配器可能采用不同版本线。
官方仓库未归档,核验时最近可见的代码活动日期为 2026 年 7 月 29 日。该状态表示项目近期仍有公开维护活动,不代表固定发布频率或长期支持承诺。