SuperAPI AI 工具集

tanstack-query-v5

编程与开发

查看项目

更新于 10-15 14:20

技能介绍

用途

TanStack Query v5(React Query)用于异步操作、数据获取、缓存和状态管理。适用于获取服务端数据、管理异步操作、变更(mutations)、无限查询和乐观更新。

不涵盖:URL 状态管理(应使用 nuqs)、分组同步状态(应使用 ahooks.useSetState)、localStorage 持久化(应使用 ahooks.useLocalStorageState)。

前提假设

  • TanStack Query v5+
  • React 18+,支持 hooks
  • TypeScript v5+(用于类型推断)
  • 在共享查询模块中使用手写的查询键元组(query-key-factory 为可选)
  • 在应用根部配置 QueryClientProvider

原则

  • 任何能从缓存或状态管理中受益的异步操作都应使用 TanStack Query
  • 优先使用手写的查询键元组,与 hooks 同置或放在共享的 queries/ 模块中
  • 绝不手动管理 isLoading、error 或 isError 状态(由 hooks 提供)
  • queryFn 可以是任何返回 Promise 的函数(不限于 HTTP 调用)
  • 所有 TanStack 特性对任何数据源都同样有效:缓存、去重、后台重新获取、stale-while-revalidate

约束

必须:

  • 使用稳定、分层的查询键(手写元组或可选的 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 规则)

模式

手写查询键

同置、稳定的查询键:

export const userKeys = { all: ['users'] as const, detail: (id: string) => [...userKeys.all, id] as const, list: (filters?: Filters) => [...userKeys.all, 'list', filters] as const, }

// 用法 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 调用:

// 服务端数据获取(传统) const { data } = useQuery({ queryKey: ['users', userId], queryFn: () => fetchUser(userId), })

// 本地计算(无网络) const { data } = useQuery({ queryKey: ['fibonacci', n], queryFn: () => computeFibonacci(n), })

// localStorage 读取 const { data: settings } = useQuery({ queryKey: ['user-settings'], queryFn: () => Promise.resolve( JSON.parse(localStorage.getItem('settings') || '{}') ), })

// 文件操作 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 分钟 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 - 查询键工厂库
  • React 规则 - 状态管理决策树和模式
  • ahooks - 用于同步状态的工具 hooks
  • OpenAPI 集成 - 生成的客户端模式