tanstack-query-v5

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Skill: tanstack-query

Skill: tanstack-query

Scope

范围

  • Applies to: TanStack Query v5+ for async operations, data fetching, caching, mutations, infinite queries, optimistic updates
  • Does NOT cover: URL state management (use nuqs), grouped synchronous state (use ahooks.useSetState), localStorage persistence (use ahooks.useLocalStorageState)
  • 适用于:TanStack Query v5+ 的异步操作、数据获取、缓存、mutations、infinite queries、乐观更新
  • 不涵盖:URL 状态管理(use nuqs)、分组同步状态(ahooks.useSetState)、localStorage 持久化(ahooks.useLocalStorageState)

Assumptions

前提条件

  • TanStack Query v5+
  • React 18+ with hooks support
  • TypeScript v5+ (for type inference)
  • Handwritten query-key tuples in shared query modules (query-key-factory is optional)
  • QueryClientProvider configured at app root
  • TanStack Query v5+
  • 支持 hooks 的 React 18+
  • TypeScript v5+(用于类型推断)
  • 在共享 query modules 中手写 query-key 元组(query-key-factory 可选)
  • 已在 app root 配置 QueryClientProvider

Principles

原则

  • Use TanStack Query for ANY async operation that benefits from caching or state management
  • Prefer handwritten query-key tuples colocated with hooks or in a shared
    queries/
    module
  • Never manually manage
    isLoading
    ,
    error
    , or
    isError
    states (provided by hooks)
  • queryFn
    can be any Promise-returning function (not just HTTP calls)
  • All TanStack features work identically regardless of data source: caching, deduping, background refetching, stale-while-revalidate
  • 对任何可从缓存或状态管理中获益的异步操作使用 TanStack Query
  • 优先使用与 hooks 放在一起或置于共享
    queries/
    模块中的手写 query-key 元组
  • 绝不手动管理
    isLoading
    、
    error
    或
    isError
    状态(这些状态由 hooks 提供)
  • queryFn
    可以是任何返回 Promise 的函数(不仅仅是 HTTP calls)
  • 无论 data source 如何,所有 TanStack 特性都以相同方式工作:caching、deduping、background refetching、stale-while-revalidate

Constraints

约束

MUST

必须

  • Use stable, hierarchical query keys (handwritten tuples or optional query-key-factory)
  • Use TanStack Query hooks for async operations (never manually manage loading/error states)
  • 使用稳定、分层的 query keys(手写元组或可选的 query-key-factory)
  • 对异步操作使用 TanStack Query hooks(绝不手动管理 loading/error 状态)

SHOULD

应该

  • Use TanStack Query for any async operation that benefits from caching (HTTP, localStorage reads, local computations, file operations)
  • Extract query logic into custom hooks for reusability
  • Combine multiple queries into cohesive hooks
  • Use TypeScript generics for type-safe queries:
    useQuery<ResponseType>(...)
  • Configure QueryClient defaults (retry, staleTime) at provider level
  • Use
    @lukemorales/query-key-factory
    when the project already standardizes on it
  • 对任何可从缓存中获益的异步操作使用 TanStack Query(HTTP、localStorage 读取、本地计算、文件操作)
  • 将 query 逻辑提取到 custom hooks 中以便复用
  • 将多个 queries 合并为内聚的 hooks
  • 使用 TypeScript generics 实现类型安全的 queries:
    useQuery<ResponseType>(...)
  • 在 provider 层配置 QueryClient defaults(retry、staleTime)
  • 当项目已经标准化使用
    @lukemorales/query-key-factory
    时使用它

AVOID

避免

  • Hardcoding unrelated query keys in invalidations without a shared prefix
  • Manually managing loading/error states (use hook-provided states)
  • Using for URL-shareable state (use nuqs instead)
  • Using for grouped synchronous state (use ahooks.useSetState instead)
  • Using for one-off promises without caching needs (use plain async/await in event handlers)
  • 在 invalidations 中硬编码不相关且没有共享 prefix 的 query keys
  • 手动管理 loading/error 状态(应使用 hooks 提供的状态)
  • 用于可通过 URL 分享的状态(应改用 nuqs)
  • 用于分组的同步状态(应改用 ahooks.useSetState)
  • 用于无需缓存的一次性 Promise(在事件处理器中使用普通 async/await)

Interactions

交互关系

  • Complements nuqs-v2 for URL state management (queries can depend on URL params)
  • Complements ahooks-v3 for synchronous state (useSetState for form state, useLocalStorageState for persistence)
  • Works with OpenAPI-generated API clients; React Query hooks are handwritten against those clients
  • Part of state management decision tree (see React rules)
  • 与 nuqs-v2 配合用于 URL 状态管理(queries 可以依赖 URL params)
  • 与 ahooks-v3 配合用于同步状态(useSetState 用于表单状态,useLocalStorageState 用于持久化)
  • 可与 OpenAPI 生成的 API clients 配合使用;React Query hooks 基于这些 clients 手写
  • 是状态管理 decision tree 的一部分(参见 React rules)

Patterns

模式

Handwritten Query Keys

手写 Query Keys

Colocated, stable query keys:
typescript
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),
})
位于同一位置且稳定的 query keys:
typescript
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),
})

Optional Query Key Factory

可选 Query Key Factory

When the project already uses
@lukemorales/query-key-factory
:
typescript
import { createQueryKeys } from '@lukemorales/query-key-factory'

export const users = createQueryKeys('users', {
  detail: (id: string) => ({
    queryKey: [id],
    queryFn: () => fetchUser(id),
  }),
})
当项目已经使用
@lukemorales/query-key-factory
时:
typescript
import { createQueryKeys } from '@lukemorales/query-key-factory'

export const users = createQueryKeys('users', {
  detail: (id: string) => ({
    queryKey: [id],
    queryFn: () => fetchUser(id),
  }),
})

Versatile queryFn Pattern

通用 queryFn 模式

TanStack Query accepts ANY async
queryFn
—not just HTTP calls:
typescript
// 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),
})
When to use: Any async operation that benefits from automatic caching, stale management, or repeated execution
When NOT to use: One-off promises without caching needs (use plain async/await in event handlers)
TanStack Query 接受任何异步
queryFn
,而不仅仅是 HTTP calls:
typescript
// 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),
})
何时使用:任何可从自动缓存、stale 状态管理或重复执行中获益的异步操作
何时不使用:无需缓存的一次性 Promise(在事件处理器中使用普通 async/await)

Mutation Pattern

Mutation 模式

Mutations with automatic invalidation:
typescript
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 })
    },
  })
}
带自动 invalidation 的 mutations:
typescript
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 })
    },
  })
}

Infinite Query Pattern

Infinite Query 模式

Pagination with infinite scroll:
typescript
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,
})
使用无限滚动实现分页:
typescript
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 Configuration

QueryClient 配置

Configure defaults at provider level:
typescript
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>
  )
}
在 provider 层配置 defaults:
typescript
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>
  )
}

Handwritten Hook Pattern

手写 Hook 模式

Wrap the generated OpenAPI client in custom hooks:
typescript
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),
  })
}
将生成的 OpenAPI client 包装到 custom hooks 中:
typescript
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),
  })
}

Custom Hook Pattern

Custom Hook 模式

Extract query logic into reusable hooks:
typescript
// 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),
  })
}
将 query 逻辑提取到可复用 hooks 中:
typescript
// 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),
  })
}

State Management Decision Tree

状态管理 Decision Tree

  1. URL-shareable state → Use
    nuqs
    (filters, search, tabs, pagination)
  2. Grouped state not in URL → Use
    ahooks.useSetState
    (form state, game engine, ephemeral UI)
  3. Async operations → Use TanStack Query (data fetching, mutations, caching)
  4. localStorage persistence → Use
    ahooks.useLocalStorageState
    (preferences, settings)
  5. Simple independent state → Use
    useState
    (rare, prefer other options)
Use TanStack Query for: Server data fetching, mutations, optimistic updates, background refetching, caching, any async operation that benefits from state management
Don't use TanStack Query for: URL-shareable state (use nuqs), grouped synchronous state (use ahooks.useSetState), one-off promises without caching needs
  1. 可通过 URL 分享的状态 → 使用
    nuqs
    (筛选、搜索、tabs、分页)
  2. 不在 URL 中的分组状态 → 使用
    ahooks.useSetState
    (表单状态、游戏引擎、临时 UI)
  3. 异步操作 → 使用 TanStack Query(数据获取、mutations、缓存)
  4. localStorage 持久化 → 使用
    ahooks.useLocalStorageState
    (首选项、设置)
  5. 简单的独立状态 → 使用
    useState
    (很少使用,优先选择其他方案)
使用 TanStack Query 处理:服务器数据获取、mutations、乐观更新、后台重新获取、缓存,以及任何可从状态管理中获益的异步操作
不要使用 TanStack Query 处理:可通过 URL 分享的状态(改用 nuqs)、分组的同步状态(改用 ahooks.useSetState)、无需缓存的一次性 Promise

References

参考

  • TanStack Query documentation - Official documentation
  • Query Key Factory - Query key factory library
  • React rules - State management decision tree and patterns
  • ahooks - Utility hooks for synchronous state
  • OpenAPI Integration - Generated client patterns
  • TanStack Query 文档 - 官方文档
  • Query Key Factory - Query key factory 库
  • React rules - 状态管理决策树和模式
  • ahooks - 用于同步状态的实用 hooks
  • OpenAPI Integration - 生成的 client 模式