关于 tanstack-query-v5
面向使用 React 与 TypeScript 的开发者,提供 TanStack Query v5 异步数据管理模式指南。内容涵盖查询键、缓存、变更和无限查询,并区分适用与不适用的状态场景。
主要功能
- 01稳定的分层查询键
- 02异步操作缓存与重取
- 03变更与查询失效
- 04无限查询与分页
- 05自定义 Hook 模式
适用场景
- 01为服务端数据获取设置缓存
- 02封装可复用的查询逻辑
- 03实现变更后的数据失效
- 04构建无限滚动分页
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ blockmatic/ basilic-skills --skill tanstack-query-v5安装后在 Claude Code 中输入/tanstack-query-v5确认已加载。
详细介绍
技能:tanstack-query
适用范围
- 适用于:用于异步操作、数据获取、缓存、变更、无限查询和乐观更新的 TanStack Query v5+
- 不适用于:URL 状态管理(使用 nuqs)、分组同步状态(使用 ahooks.useSetState)、localStorage 持久化(使用 ahooks.useLocalStorageState)
假设
- TanStack Query v5+
- 支持 hooks 的 React 18+
- TypeScript v5+(用于类型推断)
- 在共享查询模块中手写查询键元组(query-key-factory 可选)
- 已在应用根节点配置 QueryClientProvider
原则
- 对任何能从缓存或状态管理中受益的异步操作,均使用 TanStack Query
- 优先使用与 hooks 同处一处的手写查询键元组,或将其放在共享的
queries/模块中 - 切勿手动管理
isLoading、error或isError状态(这些状态由 hooks 提供) queryFn可以是任何返回 Promise 的函数(不仅限于 HTTP 调用)- 无论数据源如何,所有 TanStack 功能的工作方式都相同:缓存、去重、后台重新获取、过期时重新验证
约束
必须
- 使用稳定的分层查询键(手写元组或可选的 query-key-factory)
- 对异步操作使用 TanStack Query hooks(切勿手动管理加载/错误状态)
应当
- 对任何能从缓存中受益的异步操作使用 TanStack Query(HTTP、localStorage 读取、本地计算、文件操作)
- 将查询逻辑提取到自定义 hooks 中,以便复用
- 将多个查询组合成一致的 hooks
- 使用 TypeScript 泛型实现类型安全的查询:
useQuery<ResponseType>(...) - 在 provider 层配置 QueryClient 默认值(retry、staleTime)
- 如果项目已统一使用
@lukemorales/query-key-factory,则使用它
避免
- 在失效操作中硬编码没有共享前缀且彼此无关的查询键
- 手动管理加载/错误状态(使用 hook 提供的状态)
- 将其用于可通过 URL 分享的状态(改用 nuqs)
- 将其用于分组同步状态(改用 ahooks.useSetState)
- 将其用于无需缓存的一次性 Promise(在事件处理程序中使用普通的 async/await)
配合使用
- 与 nuqs-v2 配合管理 URL 状态(查询可以依赖 URL 参数)
- 与 ahooks-v3 配合管理同步状态(使用 useSetState 管理表单状态,使用 useLocalStorageState 实现持久化)
- 可与 OpenAPI 生成的 API 客户端配合使用;React Query hooks 针对这些客户端手写
- 属于状态管理决策树的一部分(参见 React 规则)
模式
手写查询键
将稳定的查询键与 hooks 放在一处:
export const userKeys = {
all: ['users'] as const,
detail: (id: string) => [...userKeys.all, id] as const,
list: (filters?: Filters) => [...userKeys.all, 'list', filters] as const,
}
// Usage
import { useQuery } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
const { data, isLoading, error } = useQuery({
queryKey: userKeys.detail(userId),
queryFn: () => fetchUser(userId),
})
可选的查询键工厂
当项目已使用 @lukemorales/query-key-factory 时:
import { createQueryKeys } from '@lukemorales/query-key-factory'
export const users = createQueryKeys('users', {
detail: (id: string) => ({
queryKey: [id],
queryFn: () => fetchUser(id),
}),
})
多用途 queryFn 模式
TanStack Query 接受任何异步 queryFn,不仅限于 HTTP 调用:
// Server data fetching (traditional)
const { data } = useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
})
// Local computation (no network)
const { data } = useQuery({
queryKey: ['fibonacci', n],
queryFn: () => computeFibonacci(n),
})
// localStorage read
const { data: settings } = useQuery({
queryKey: ['user-settings'],
queryFn: () => Promise.resolve(
JSON.parse(localStorage.getItem('settings') || '{}')
),
})
// File operation
const { data } = useQuery({
queryKey: ['file-content', path],
queryFn: () => readFile(path),
})
适用场景:任何能从自动缓存、过期管理或重复执行中受益的异步操作
不适用场景:无需缓存的一次性 Promise(在事件处理程序中使用普通的 async/await)
变更模式
自动失效的变更:
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
function useCreateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (data: CreateUserInput) => {
const response = await createUser(data)
return response
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: userKeys.all })
},
})
}
无限查询模式
使用无限滚动进行分页:
import { useInfiniteQuery } from '@tanstack/react-query'
import { users } from '@/queries/users'
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['users', 'infinite'],
queryFn: ({ pageParam = 0 }) => fetchUsers({ page: pageParam }),
getNextPageParam: (lastPage, pages) => lastPage.nextPage,
initialPageParam: 0,
})
QueryClient 配置
在 provider 层配置默认值:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
function Providers({ children }: { children: ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 1,
refetchOnWindowFocus: false,
},
},
}))
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
}
手写 hook 模式
将生成的 OpenAPI 客户端封装在自定义 hooks 中:
import { useQuery } from '@tanstack/react-query'
import { apiClient } from '@/lib/api-client'
export function useHealthCheck() {
return useQuery({
queryKey: ['health'],
queryFn: () => apiClient.GET('/health').then(r => r.data),
})
}
自定义 hook 模式
将查询逻辑提取到可复用的 hooks 中:
// hooks/useUser.ts
import { useQuery } from '@tanstack/react-query'
import { userKeys } from '@/queries/users'
export function useUser(userId: string) {
return useQuery({
queryKey: userKeys.detail(userId),
queryFn: () => fetchUser(userId),
})
}
状态管理决策树
- 可通过 URL 分享的状态 → 使用
nuqs(筛选器、搜索、标签页、分页) - 未存储在 URL 中的分组状态 → 使用
ahooks.useSetState(表单状态、游戏引擎、临时 UI) - 异步操作 → 使用 TanStack Query(数据获取、变更、缓存)
- localStorage 持久化 → 使用
ahooks.useLocalStorageState(偏好设置、设置) - 简单的独立状态 → 使用
useState(少见,优先选择其他方案)
使用 TanStack Query 的场景:服务端数据获取、变更、乐观更新、后台重新获取、缓存,以及任何能从状态管理中受益的异步操作
不要将 TanStack Query 用于:可通过 URL 分享的状态(使用 nuqs)、分组同步状态(使用 ahooks.useSetState)、无需缓存的一次性 Promise
参考资料
- TanStack Query 文档 - 官方文档
- Query Key Factory - Query key 工厂库
- React 规则 - 状态管理决策树和模式
- ahooks - 用于同步状态的实用 Hooks
- OpenAPI 集成 - 生成的客户端模式
评论
支持 Markdown