整体心智模型:TanStack Query 是你的“数据后台”
你可以把 TanStack Query 想成一个独立的数据后台:组件只是来“取数”和“下指令”,真正记住数据、重复利用数据、决定要不要再请求的,是它自己。
这一节先把三个核心角色讲清楚:Query / Mutation / QueryClient,后面所有缓存、生命周期都是围绕它们转。
参考官方的 Quick Start 和文档可以看到这些概念的身影,比如 useQuery、useMutation 和 QueryClient 的用法都在这里有例子说明:Quick Start、QueryClient 文档。
Query / Mutation / QueryClient:读 vs 写
-
Query:负责“读数据”的单元,一般对应一个
useQuery调用- 有一个 query key(标识是谁)
- 有一个 queryFn(怎么去拿数据)
- 有自己的 缓存数据、状态(loading/error/success)、stale 信息
-
Mutation:负责“写数据”的单元,一般对应一个
useMutation调用- 例如创建、更新、删除 post
- 通常会在
onSuccess里配合queryClient.invalidateQueries去刷新相关 Query
-
QueryClient:整个应用层面的“数据控制台”
- 管所有 Query 的缓存、垃圾回收、失效、重新拉取
- 暴露很多方法:
invalidateQueries、prefetchQuery、getQueryData等 - React 里一般通过
<QueryClientProvider client={queryClient}>注入
一个最小可运行例子
基于官方 Quick Start 的简化版本(v5 写法):
tsx// main.tsx / App.tsx 顶层
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { PostsPage } from './PostsPage'
const queryClient = new QueryClient()
export function App() {
return (
<QueryClientProvider client={queryClient}>
<PostsPage />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
tsx// PostsPage.tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import axios from 'axios'
function fetchPosts() {
return axios.get('https://jsonplaceholder.typicode.com/posts').then(r => r.data)
}
function createPostApi(newPost: { title: string; body: string }) {
return axios.post('https://jsonplaceholder.typicode.com/posts', newPost).then(r => r.data)
}
export function PostsPage() {
const queryClient = useQueryClient()
// Query:读 posts
const { data: posts, isLoading, isError } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
})
// Mutation:写(创建) post
const createPost = useMutation({
mutationFn: createPostApi,
onSuccess: () => {
// 写成功后,让所有 ['posts'] 相关 Query 失效,触发 refetch
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
if (isLoading) return <div>Loading...</div>
if (isError) return <div>Error</div>
return (
<div>
<button
onClick={() =>
createPost.mutate({ title: 'Hello', body: 'TanStack Query' })
}
>
新建 Post
</button>
<ul>
{posts.map((p: any) => (
<li key={p.id}>{p.title}</li>
))}
</ul>
</div>
)
}
useQuery部分就是一个 Query 实例useMutation部分负责写操作QueryClient则被useQuery/useMutation在内部使用,来管理缓存与失效逻辑
Query 的完整生命周期(mount → fetch → cache → stale → gc)
来看一眼 Query 的生命周期流程,你后面要能口头说清楚这条链:
Rendering diagram…
- 挂载(mount):组件第一次调用
useQuery,Query 建立或被订阅 - 查缓存:先看
queryKey对应的缓存里有没有数据 - 发请求(fetch):没有缓存或需要刷新,会执行
queryFn - 写缓存(cache):请求成功,结果放到缓存里,并更新所有订阅它的组件
- 新鲜期(fresh):在
staleTime时间内,Query 被认为是“新鲜的” - 变陈旧(stale):超过
staleTime,进入 stale 状态,但数据还在 - 无订阅 + 超过 gcTime:当没有组件在用它,并且超出垃圾回收时间,就被 GC 删除
生命周期的具体机制在官方文档和一些实践文章里写得比较清楚,可以结合 TanStack Query 官网 和这篇实践总结一起理解:From Beginner to Pro: Mastering State Management with TanStack Query v5。
1 / 7