框架
版本

TypeScript

React Query 现已用 TypeScript 编写,以确保库和您的项目都是类型安全的!

需要注意的事项

  • 当前类型要求使用 TypeScript v4.7 或更高版本
  • 此仓库中类型的更改被视为非破坏性更改,通常作为补丁语义版本更改发布(否则每次类型增强都会是主要版本!)
  • 强烈建议您将 `react-query` 包版本锁定到特定的补丁版本,并在升级时预期类型可能会在任何版本之间进行修复或升级
  • React Query 的非类型相关公共 API 仍然严格遵循语义版本控制。

类型推断

React Query 中的类型通常流转得非常好,因此您无需自行提供类型注释

tsx
const { data } = useQuery({
  //    ^? const data: number | undefined
  queryKey: ['test'],
  queryFn: () => Promise.resolve(5),
})
const { data } = useQuery({
  //    ^? const data: number | undefined
  queryKey: ['test'],
  queryFn: () => Promise.resolve(5),
})

TypeScript Playground

tsx
const { data } = useQuery({
  //      ^? const data: string | undefined
  queryKey: ['test'],
  queryFn: () => Promise.resolve(5),
  select: (data) => data.toString(),
})
const { data } = useQuery({
  //      ^? const data: string | undefined
  queryKey: ['test'],
  queryFn: () => Promise.resolve(5),
  select: (data) => data.toString(),
})

TypeScript Playground

如果您的 queryFn 有明确定义的返回类型,则效果最佳。请记住,大多数数据获取库默认返回 any,因此请务必将其提取到一个类型正确的函数中

tsx
const fetchGroups = (): Promise<Group[]> =>
  axios.get('/groups').then((response) => response.data)

const { data } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
//      ^? const data: Group[] | undefined
const fetchGroups = (): Promise<Group[]> =>
  axios.get('/groups').then((response) => response.data)

const { data } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
//      ^? const data: Group[] | undefined

TypeScript Playground

类型缩小

React Query 使用 可辨识联合类型 来表示查询结果,通过 status 字段和派生的状态布尔标志进行辨识。这将允许您检查,例如 success 状态,以使 data 被定义

tsx
const { data, isSuccess } = useQuery({
  queryKey: ['test'],
  queryFn: () => Promise.resolve(5),
})

if (isSuccess) {
  data
  //  ^? const data: number
}
const { data, isSuccess } = useQuery({
  queryKey: ['test'],
  queryFn: () => Promise.resolve(5),
})

if (isSuccess) {
  data
  //  ^? const data: number
}

TypeScript Playground

错误字段的类型定义

error 的类型默认为 Error,因为这是大多数用户所期望的。

tsx
const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
//      ^? const error: Error
const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
//      ^? const error: Error

TypeScript Playground

如果您想抛出自定义错误,或者根本不是 Error 的东西,您可以指定 error 字段的类型

tsx
const { error } = useQuery<Group[], string>(['groups'], fetchGroups)
//      ^? const error: string | null
const { error } = useQuery<Group[], string>(['groups'], fetchGroups)
//      ^? const error: string | null

然而,这有一个缺点,即 useQuery 的所有其他泛型的类型推断将不再起作用。抛出不是 Error 的东西通常不被认为是好的做法,因此如果您有一个像 AxiosError 这样的子类,您可以使用类型缩小来使 error 字段更具体

tsx
import axios from 'axios'

const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
//      ^? const error: Error | null

if (axios.isAxiosError(error)) {
  error
  // ^? const error: AxiosError
}
import axios from 'axios'

const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
//      ^? const error: Error | null

if (axios.isAxiosError(error)) {
  error
  // ^? const error: AxiosError
}

TypeScript Playground

注册全局错误类型

TanStack Query v5 允许通过修改 Register 接口来为所有内容设置全局错误类型,而无需在调用端指定泛型。这将确保类型推断仍然有效,但错误字段将是指定的类型。如果您想强制调用端必须进行显式类型缩小,请将 defaultError 设置为 unknown

tsx
import '@tanstack/react-query'

declare module '@tanstack/react-query' {
  interface Register {
    // Use unknown so call sites must narrow explicitly.
    defaultError: unknown
  }
}

const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
//      ^? const error: unknown | null
import '@tanstack/react-query'

declare module '@tanstack/react-query' {
  interface Register {
    // Use unknown so call sites must narrow explicitly.
    defaultError: unknown
  }
}

const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups })
//      ^? const error: unknown | null

元数据的类型定义

注册全局元数据类型

类似于注册全局错误类型,您也可以注册全局 Meta 类型。这确保了 查询变更 上可选的 meta 字段保持一致且类型安全。请注意,注册的类型必须扩展 Record<string, unknown>,以便 meta 仍然是一个对象。

ts
import '@tanstack/react-query'

interface MyMeta extends Record<string, unknown> {
  // Your meta type definition.
}

declare module '@tanstack/react-query' {
  interface Register {
    queryMeta: MyMeta
    mutationMeta: MyMeta
  }
}
import '@tanstack/react-query'

interface MyMeta extends Record<string, unknown> {
  // Your meta type definition.
}

declare module '@tanstack/react-query' {
  interface Register {
    queryMeta: MyMeta
    mutationMeta: MyMeta
  }
}

查询和变更键的类型定义

注册查询和变更键类型

与注册全局错误类型类似,您还可以注册全局 QueryKeyMutationKey 类型。这允许您为键提供更多的结构,以匹配应用程序的层次结构,并使其在库的所有表面区域都具有类型。请注意,注册的类型必须扩展 Array 类型,以便您的键仍然是数组。

ts
import '@tanstack/react-query'

type QueryKey = ['dashboard' | 'marketing', ...ReadonlyArray<unknown>]

declare module '@tanstack/react-query' {
  interface Register {
    queryKey: QueryKey
    mutationKey: QueryKey
  }
}
import '@tanstack/react-query'

type QueryKey = ['dashboard' | 'marketing', ...ReadonlyArray<unknown>]

declare module '@tanstack/react-query' {
  interface Register {
    queryKey: QueryKey
    mutationKey: QueryKey
  }
}

查询选项的类型定义

如果您将查询选项内联到 useQuery 中,您将获得自动类型推断。但是,您可能希望将查询选项提取到一个单独的函数中,以便在 useQuery 和例如 prefetchQuery 之间共享它们。在这种情况下,您将失去类型推断。要恢复它,您可以使用 queryOptions 辅助函数

ts
import { queryOptions } from '@tanstack/react-query'

function groupOptions() {
  return queryOptions({
    queryKey: ['groups'],
    queryFn: fetchGroups,
    staleTime: 5 * 1000,
  })
}

useQuery(groupOptions())
queryClient.prefetchQuery(groupOptions())
import { queryOptions } from '@tanstack/react-query'

function groupOptions() {
  return queryOptions({
    queryKey: ['groups'],
    queryFn: fetchGroups,
    staleTime: 5 * 1000,
  })
}

useQuery(groupOptions())
queryClient.prefetchQuery(groupOptions())

此外,从 queryOptions 返回的 queryKey 知道与之关联的 queryFn,我们可以利用这些类型信息来使 queryClient.getQueryData 等函数也了解这些类型

ts
function groupOptions() {
  return queryOptions({
    queryKey: ['groups'],
    queryFn: fetchGroups,
    staleTime: 5 * 1000,
  })
}

const data = queryClient.getQueryData(groupOptions().queryKey)
//     ^? const data: Group[] | undefined
function groupOptions() {
  return queryOptions({
    queryKey: ['groups'],
    queryFn: fetchGroups,
    staleTime: 5 * 1000,
  })
}

const data = queryClient.getQueryData(groupOptions().queryKey)
//     ^? const data: Group[] | undefined

如果没有 queryOptionsdata 的类型将是 unknown,除非我们向其传递泛型

ts
const data = queryClient.getQueryData<Group[]>(['groups'])
const data = queryClient.getQueryData<Group[]>(['groups'])

变更选项的类型定义

queryOptions 类似,您可以使用 mutationOptions 将变更选项提取到单独的函数中

ts
function groupMutationOptions() {
  return mutationOptions({
    mutationKey: ['addGroup'],
    mutationFn: addGroup,
  })
}

useMutation({
  ...groupMutationOptions(),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['groups'] }),
})
useIsMutating(groupMutationOptions())
queryClient.isMutating(groupMutationOptions())
function groupMutationOptions() {
  return mutationOptions({
    mutationKey: ['addGroup'],
    mutationFn: addGroup,
  })
}

useMutation({
  ...groupMutationOptions(),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['groups'] }),
})
useIsMutating(groupMutationOptions())
queryClient.isMutating(groupMutationOptions())

使用 skipToken 类型安全地禁用查询

如果您正在使用 TypeScript,您可以使用 skipToken 来禁用查询。当您想根据条件禁用查询但仍希望查询保持类型安全时,这非常有用。有关更多信息,请参阅禁用查询指南。

延伸阅读

有关类型推断的技巧和窍门,请参阅社区资源中的 React Query 和 TypeScript。要了解如何获得最佳的类型安全性,您可以阅读 类型安全的 React Query查询选项 API 概述了类型推断如何与 queryOptions 辅助函数一起工作。