TanStack Query 数据请求与缓存
TanStack Query 数据请求与缓存:服务端状态的一体化方案
引言
「把 API 数据放进 useState + useEffect」是 React 新手的第一课,也是无数生产事故的温床:竞态条件、重复请求、缓存失效、loading/error 状态散落各处……TanStack Query(曾用名 React Query)正是为此而生——它把「服务端状态」从组件中抽离,用一套完整的状态机 + 缓存系统统一管理。本文将从核心概念、缓存生命周期、派生查询、失效策略到性能优化,系统拆解这套方案。
一、核心概念:Query、Mutation 与 QueryClient
TanStack Query 的三个基本单元:Query(读操作,有缓存)、Mutation(写操作,无缓存)、QueryClient(容器,管理缓存与默认配置)。
三者关系
flowchart TB
classDef q1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef q2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef q3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A[QueryClientProvider] --> B[QueryClient 实例]
B --> C[Query 缓存 Map]
B --> D[Mutation 队列]
B --> E[默认配置 defaultOptions]
C --> F[useQuery 组件订阅]
C --> G[prefetchQuery 预取]
D --> H[useMutation 提交]
H --> I[onSuccess 使缓存失效]
click C "https://tanstack.com/query/latest/docs/framework/react/reference/useQuery" "useQuery 文档"
class A q1
class B q1
class C q2
class D q2
class E q2
class F q3
class G q3
class H q3
class I q3
QueryKey:缓存的寻址方式
每个 Query 由一个 QueryKey(数组)唯一标识。key 是「缓存地址」,不是「请求标识」——同名不同参数(如 ['posts', page])会产生不同缓存条目。
flowchart LR
classDef k1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef k2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef k3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A["queryKey: ['posts', { page: 1 }]"] --> B[缓存键序列化]
B --> C[缓存条目 A]
D["queryKey: ['posts', { page: 2 }]"] --> E[缓存条目 B]
F["queryKey: ['user', 42]"] --> G[缓存条目 C]
H["invalidateQueries 按前缀匹配"] --> I[前缀匹配 全部失效]
click H "https://tanstack.com/query/latest/docs/framework/react/guides/invalidations" "失效策略文档"
class A k1
class B k1
class C k2
class D k1
class E k2
class F k1
class G k2
class H k3
class I k3
实践要点:QueryKey 是分层组织的最佳方式——['posts', 'detail', slug]、['user', 'profile']。失效时用前缀匹配 ['posts'] 即可批量失效列表与详情。
二、查询生命周期与状态机
TanStack Query 的核心机制是陈旧数据(Stale-While-Revalidate):缓存的数据可以立即展示(即使已过期),同时后台静默刷新。这让界面永远「有内容」,而不是转圈。
查询状态机
stateDiagram-v2
direction LR
[*] --> Pending: 首次挂载
Pending --> Success: 请求成功
Pending --> Error: 请求失败
Success --> Refetching: 手动/自动重取
Refetching --> Success: 成功
Refetching --> Error: 失败
Success --> Error: 后台刷新失败
Error --> Pending: 重试触发
Error --> Success: 重试成功
note right of Pending
无缓存数据时显示 loading
有缓存时直接展示缓存
end note
缓存的四种状态
| 状态 | 说明 | 默认行为 |
|---|---|---|
fresh |
新鲜数据,无需重取 | staleTime 内直接使用 |
stale |
过期数据 | 展示 + 后台重取 |
fetching |
请求进行中 | 数据展示同时刷新 |
paused |
请求被暂停 | 网络恢复自动重试 |
完整生命周期时序
sequenceDiagram
participant C as 组件
participant Q as Query
participant S as 服务端
C->>Q: useQuery 挂载
Q->>Q: 查询缓存
alt 有缓存且未过期
Q-->>C: 立即返回缓存
else 有缓存但过期
Q-->>C: 返回缓存(stale)
Q->>S: 后台重新请求
S-->>Q: 新数据
Q-->>C: 更新并通知
else 无缓存
Q->>S: 发起请求
S-->>Q: 数据
Q-->>C: 返回并缓存
end
C->>Q: 组件卸载
Q->>Q: gcTime 后回收缓存
三、失效与更新策略
服务端状态的难点在于何时失效。TanStack Query 提供四层失效手段,从粗到细:
失效手段对比
flowchart TB
classDef i1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef i2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef i3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A[失效时机] --> B[invalidateQueries<br/>按 key 前缀批量失效]
A --> C[setQueryData<br/>直接写入缓存]
A --> D[fetchQuery<br/>强制重取]
A --> E[refetchInterval<br/>轮询刷新]
B --> F[写操作成功后调用]
C --> G[乐观更新 回滚机制]
D --> H[关键数据强制刷新]
E --> I[实时数据 如在线状态]
click B "https://tanstack.com/query/latest/docs/framework/react/guides/invalidations" "失效文档"
class A i1
class B i1
class C i2
class D i2
class E i2
class F i3
class G i3
class H i3
class I i3
乐观更新的完整流程
乐观更新(Optimistic Update)让 UI 在服务端确认前就展示新状态,再根据结果提交或回滚:
sequenceDiagram
participant U as 用户
participant C as 组件
participant Q as Query 缓存
participant S as API
U->>C: 提交变更
C->>Q: 立即写入新值(乐观)
Q-->>C: UI 即时更新
C->>S: 发起真实请求
alt 成功
S-->>C: 200
C->>Q: 用服务端响应覆盖
else 失败
S-->>C: 错误
C->>Q: 回滚到旧值
C->>C: 显示错误提示
end
实现建议:useMutation 的 onMutate 中执行乐观写入并返回「回滚函数」,onError 中调用回滚,onSettled 中统一 invalidateQueries 对齐真实数据。
四、派生查询与组合模式
真实应用很少是单一查询——列表 + 筛选 + 详情 + 关联推荐,查询之间存在派生与依赖关系。
组合查询模式
flowchart TB
classDef c1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef c2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef c3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A[帖子详情页] --> B[主查询 postDetail]
B --> C{依赖主查询数据}
C -->|评论列表| D[useQuery enabled 条件]
C -->|作者信息| E[useQuery 参数来自主数据]
C -->|相关推荐| F[独立并行查询]
D --> G[评论数据]
E --> H[作者数据]
F --> I[推荐数据]
click D "https://tanstack.com/query/latest/docs/framework/react/guides/dependent-queries" "依赖查询文档"
class A c1
class B c1
class C c1
class D c2
class E c2
class F c2
class G c3
class H c3
class I c3
常用组合查询 API
| API | 用途 | 特点 |
|---|---|---|
useQuery |
单一数据源 | 最常用 |
useQueries |
动态并行查询 | 数量运行时确定 |
useInfiniteQuery |
分页/无限滚动 | 游标推进 |
enabled |
条件查询 | 依赖前置查询 |
placeholderData |
占位数据 | 保持上次数据平滑过渡 |
initialData |
初始数据 | 服务端注入兜底 |
五、性能优化实践
请求去重与窗口聚焦
TanStack Query 内置请求去重:同一 QueryKey 并发挂载多个组件,只会发起一次请求。此外,refetchOnWindowFocus 默认开启——用户切回标签页时自动刷新数据,适合「价格、库存、消息数」等易变场景;若数据变化不敏感,可关闭以省流量。
性能优化清单
flowchart TD
classDef p1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef p2 fill:#fff3e0,stroke:#f57c00,color:#e65100
classDef p3 fill:#e8f5e9,stroke:#388e3c,color:#1b5e20
A[Query 性能优化] --> B[减少请求]
B --> B1[staleTime 拉长]
B --> B2[请求去重 共享 QueryKey]
B --> B3[预取 prefetchQuery]
A --> C[减少渲染]
C --> C1[select 只取所需字段]
C --> C2[结构共享避免新引用]
C --> C3[缓存数据引用稳定]
A --> D[减少流量]
D --> D1[按需字段服务端裁剪]
D --> D2[条件查询 enabled]
D --> D3[关闭窗口聚焦刷新]
click C1 "https://tanstack.com/query/latest/docs/framework/react/guides/migrating-to-react-query-4#select" "select 优化文档"
class A p1
class B p1
class B1 p3
class B2 p3
class B3 p3
class C p1
class C1 p3
class C2 p3
class C3 p3
class D p1
class D1 p3
class D2 p3
class D3 p3
常见陷阱
| 陷阱 | 表现 | 对策 |
|---|---|---|
| 缓存过期不失效 | 数据一直旧 | 写后 invalidate |
| QueryKey 不一致 | 缓存永远不命中 | 统一 queryKey 工厂 |
| 忽略 error | 界面一直转圈 | 处理 isError 分支 |
| 循环 refetch | 无限请求 | 检查依赖数组 |
| 大对象 select | 重渲染频繁 | 返回原始类型/浅层对象 |
六、SSR 集成:预取与脱水
TanStack Query 与 SSR 配合的标准姿势:服务端 prefetchQuery 预热缓存,dehydrate 序列化注入 HTML,客户端 hydrate 恢复缓存。这与我们上一篇文章《React SSR 与流式渲染》中的「数据对齐」章节完全吻合。
flowchart LR
classDef s1 fill:#e3f2fd,stroke:#1976d2,color:#0d47a1
classDef s2 fill:#f3e5f5,stroke:#8e24aa,color:#4a148c
classDef s3 fill:#e0f7fa,stroke:#00838f,color:#004d40
A[服务端请求] --> B[prefetchQuery 预取]
B --> C[渲染组件 HTML]
C --> D[dehydrate 序列化缓存]
D --> E[注入 window.__REACT_QUERY_STATE__]
E --> F[客户端加载]
F --> G[hydrate 恢复缓存]
G --> H[组件直接读缓存 不重复请求]
click B "https://tanstack.com/query/latest/docs/framework/react/guides/ssr" "SSR 集成文档"
class A s1
class B s1
class C s2
class D s2
class E s3
class F s1
class G s2
class H s3
七、总结
TanStack Query 把「服务端状态」变成了一门有纪律的学科:
- 状态机驱动:pending/success/error/stale 四态清晰,UI 分支完备;
- 缓存为核心:QueryKey 寻址、SWR 策略、gcTime 回收;
- 失效即正确:写后失效、乐观更新、回滚机制完备;
- 派生与组合:依赖查询、无限滚动、并行查询覆盖全部场景;
- SSR 友好:预取脱水注水,双端数据天然一致。
一句话总结:把服务端数据交给 TanStack Query,把精力还给业务逻辑。下一篇我们将深入 React 性能优化专题,从渲染层面全面提升应用流畅度。
