TanStack Query 实战:前端数据请求、缓存与乐观更新

在前端项目中,数据请求与缓存一直是令人头疼的事。TanStack Query(原名 React Query)把「服务端状态」从组件里剥离出来,用统一的缓存、失效与重试机制,让前端数据请求变得可预测、可维护。

一、为什么还需要 TanStack Query

用 useState + useEffect 手写数据请求时,我们往往要重复处理 loading、error、竞态、缓存失效,还要在多个组件间手动同步同一份数据。这些「服务端状态」与「客户端状态」(如表单输入、开关)有本质区别:服务端状态属于后端、会被多人修改、需要失效与重新拉取。

TanStack Query 的核心价值,就是接管这些服务端状态:它用 queryKey 作为缓存索引,自动去重并发请求、管理新鲜度、处理失败重试,并天然支持乐观更新。配合一份规范的后端 API(例如用 FastAPI 落地 Python 异步接口),前后端协作会顺畅很多。

二、快速上手

2.1 安装

npm install @tanstack/react-query
# 或
pnpm add @tanstack/react-query

2.2 挂载 QueryClientProvider

import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'

const queryClient = new QueryClient()

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserList />
    </QueryClientProvider>
  )
}

2.3 发起第一个查询

function UserList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then((r) => r.json()),
  })

  if (isLoading) return <p>加载中…</p>
  if (error) return <p>请求失败</p>
  return (
    <ul>{data.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
  )
}

注意:queryKey 是缓存的唯一索引,相同 key 会自动复用缓存与去重请求,这也是它比「每次 useEffect 里写 fetch」强的地方。

三、理解 queryKey 与缓存生命周期

缓存行为由 staleTime(新鲜期,期间不重新请求)和 gcTime(回收期,无引用后多久清理)控制。在 QueryClient 里统一配置默认值,比在每个 hook 里重复写更省心:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30_000,             // 30s 内视为新鲜,不重新请求
      gcTime: 5 * 60_000,           // 5min 无引用才回收缓存
      retry: 2,                     // 失败重试 2 次
      refetchOnWindowFocus: false,  // 窗口聚焦不强制刷新
    },
  },
})

带参数的查询要把参数写进 queryKey,例如 queryKey: ['user', id],这样不同 id 各自缓存、互不干扰。

四、数据变更与乐观更新

写操作交给 useMutation。真正的体验提升来自「乐观更新」:在请求发出前就先更新缓存,失败再回滚,成功后再失效重新拉取。

const mutation = useMutation({
  mutationFn: (newUser) =>
    fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(newUser),
    }).then((r) => r.json()),
  onMutate: async (newUser) => {
    await queryClient.cancelQueries({ queryKey: ['users'] })
    const prev = queryClient.getQueryData(['users'])
    queryClient.setQueryData(['users'], (old) => [...old, newUser])
    return { prev }
  },
  onError: (_e, _u, ctx) => {
    if (ctx?.prev) queryClient.setQueryData(['users'], ctx.prev)
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] })
  },
})

这套「取消旧请求 → 暂存快照 → 乐观写入 → 失败回滚 → 成功后失效」的模式,是 TanStack Query 在生产环境最常用的写法。

五、Vue 生态同样好用

如果你用的是 Vue 3 而非 React,直接安装 @tanstack/vue-query,API 与 React 版几乎一致,只是组件换成 useQuery 的组合式写法。同一套缓存心智模型可以同时覆盖两个技术栈,迁移成本极低。

六、生产环境最佳实践

检查项推荐配置说明
缓存新鲜度 staleTime30s–5min避免频繁打扰后端,热点数据可更长
失败重试 retry网络错误重试 1–2 次5xx 谨慎重试,避免雪崩
并行请求useQueries 批量拉取减少请求瀑布流
占位数据 placeholderDatakeepPreviousData分页切换更顺滑
依赖请求 enabled条件触发避免无谓请求
缓存回收 gcTime≥5min平衡内存占用与命中率

七、三个常见踩坑

1. queryKey 不稳定。把对象或函数直接塞进 key 会导致每次渲染都生成新 key,缓存永远命中不了。一律用可序列化、稳定的数组(如 ['user', id])。

2. 又在 useEffect 里手写 fetch。这会重新引入 loading/error 样板与竞态问题,等于放弃了缓存层。新项目应默认走 useQuery。

3. 乐观更新忘记回滚。只写 onMutate 不写 onError 回滚,一旦接口报错界面就会停留在错误数据上。务必成对出现。

八、小结

TanStack Query 不是「又一个请求库」,而是服务端状态的管理层。它用缓存索引、新鲜度策略和乐观更新,把前端数据请求从重复样板中解放出来。配合容器化的部署(如 Docker 编排)与 GitHub Actions 的自动化测试,就能交付稳定可维护的前端数据层;日常开发中用 VS Code 配合类型提示,体验会更顺滑。

上一篇 Kubernetes 准入控制实战:用 ValidatingAdmissionPolicy 守好集群边界
下一篇 Redpanda 实战:Kafka 兼容的高性能流数据平台
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]