TanStack Query核心机制与缓存心智模型

整体心智模型:TanStack Query 是你的“数据后台”

你可以把 TanStack Query 想成一个独立的数据后台:组件只是来“取数”和“下指令”,真正记住数据、重复利用数据、决定要不要再请求的,是它自己。

这一节先把三个核心角色讲清楚:Query / Mutation / QueryClient,后面所有缓存、生命周期都是围绕它们转。

参考官方的 Quick Start 和文档可以看到这些概念的身影,比如 useQueryuseMutationQueryClient 的用法都在这里有例子说明:Quick StartQueryClient 文档

Query / Mutation / QueryClient:读 vs 写

  • Query:负责“读数据”的单元,一般对应一个 useQuery 调用

    • 有一个 query key(标识是谁)
    • 有一个 queryFn(怎么去拿数据)
    • 有自己的 缓存数据、状态(loading/error/success)、stale 信息
  • Mutation:负责“写数据”的单元,一般对应一个 useMutation 调用

    • 例如创建、更新、删除 post
    • 通常会在 onSuccess 里配合 queryClient.invalidateQueries 去刷新相关 Query
  • QueryClient:整个应用层面的“数据控制台”

    • 管所有 Query 的缓存、垃圾回收、失效、重新拉取
    • 暴露很多方法:invalidateQueriesprefetchQuerygetQueryData
    • 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