mcpField
T
tanstack-query-v5

tanstack-query-v5

github.com/blockmatic开发1221 次安装5 周前更新
代码仓库

关于 tanstack-query-v5

面向使用 React 与 TypeScript 的开发者,提供 TanStack Query v5 异步数据管理模式指南。内容涵盖查询键、缓存、变更和无限查询,并区分适用与不适用的状态场景。

主要功能

  1. 01稳定的分层查询键
  2. 02异步操作缓存与重取
  3. 03变更与查询失效
  4. 04无限查询与分页
  5. 05自定义 Hook 模式

适用场景

  1. 01为服务端数据获取设置缓存
  2. 02封装可复用的查询逻辑
  3. 03实现变更后的数据失效
  4. 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),
  })
}
状态管理决策树
  1. 可通过 URL 分享的状态 → 使用 nuqs(筛选器、搜索、标签页、分页)
  2. 未存储在 URL 中的分组状态 → 使用 ahooks.useSetState(表单状态、游戏引擎、临时 UI)
  3. 异步操作 → 使用 TanStack Query(数据获取、变更、缓存)
  4. localStorage 持久化 → 使用 ahooks.useLocalStorageState(偏好设置、设置)
  5. 简单的独立状态 → 使用 useState(少见,优先选择其他方案)

使用 TanStack Query 的场景:服务端数据获取、变更、乐观更新、后台重新获取、缓存,以及任何能从状态管理中受益的异步操作

不要将 TanStack Query 用于:可通过 URL 分享的状态(使用 nuqs)、分组同步状态(使用 ahooks.useSetState)、无需缓存的一次性 Promise

参考资料

评论

支持 Markdown