框架
版本

迁移到 React Query 3

React Query 的先前版本非常棒,为库带来了许多令人惊叹的新功能、更多魔力以及整体更好的体验。它们也带来了大规模的采用,同样也为库带来了大量的改进(问题/贡献),并揭示了一些需要进一步完善以使库变得更好的地方。v3 包含了这种完善。

概述

  • 更具可扩展性和可测试性的缓存配置
  • 更好的 SSR 支持
  • 随处可用的数据延迟(以前称为 usePaginatedQuery)!
  • 双向无限查询
  • 查询数据选择器!
  • 在使用前完全配置查询和/或变更的默认值
  • 更精细的可选渲染优化
  • 新的 useQueries 钩子!(可变长度并行查询执行)
  • useIsFetching() 钩子支持查询过滤器!
  • 变更的重试/离线/重放支持
  • 在 React 之外观察查询/变更
  • 随时随地使用 React Query 核心逻辑!
  • 通过 react-query/devtools 捆绑/共置的开发者工具
  • 缓存持久化到 Web 存储(通过 react-query/persistQueryClient-experimentalreact-query/createWebStoragePersistor-experimental 进行实验)

重大更改

QueryCache 已拆分为 QueryClient 和更低级的 QueryCacheMutationCache 实例。

QueryCache 包含所有查询,MutationCache 包含所有变更,QueryClient 可用于设置配置并与它们交互。

这有一些好处:

  • 允许不同类型的缓存。
  • 具有不同配置的多个客户端可以使用相同的缓存。
  • 客户端可用于跟踪查询,这可用于 SSR 上的共享缓存。
  • 客户端 API 更侧重于一般用法。
  • 更容易测试各个组件。

创建 new QueryClient() 时,如果您不提供 QueryCacheMutationCache,它们会自动为您创建。

tsx
import { QueryClient } from 'react-query'

const queryClient = new QueryClient()
import { QueryClient } from 'react-query'

const queryClient = new QueryClient()

ReactQueryConfigProviderReactQueryCacheProvider 都已被 QueryClientProvider 取代

现在可以在 QueryClient 中指定查询和变更的默认选项:

请注意,现在是 defaultOptions 而不是 defaultConfig

tsx
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // 查询选项
    },
    mutations: {
      // 变更选项
    },
  },
})
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // 查询选项
    },
    mutations: {
      // 变更选项
    },
  },
})

QueryClientProvider 组件现在用于将 QueryClient 连接到您的应用程序:

tsx
import { QueryClient, QueryClientProvider } from 'react-query'

const queryClient = new QueryClient()

function App() {
  return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}
import { QueryClient, QueryClientProvider } from 'react-query'

const queryClient = new QueryClient()

function App() {
  return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}

默认的 QueryCache 消失了。这次是真的!

正如之前弃用通知中所述,主包中不再创建或导出默认的 QueryCache您必须通过 new QueryClient()new QueryCache()(然后可以将其传递给 new QueryClient({ queryCache }))创建自己的 QueryCache

已弃用的 makeQueryCache 工具已被删除。

它已经存在很长时间了,但最终还是消失了 :)

QueryCache.prefetchQuery() 已移至 QueryClient.prefetchQuery()

新的 QueryClient.prefetchQuery() 函数是异步的,但不会从查询中返回数据。如果您需要数据,请使用新的 QueryClient.fetchQuery() 函数

tsx
// 预取查询:
await queryClient.prefetchQuery('posts', fetchPosts)

// 获取查询:
try {
  const data = await queryClient.fetchQuery('posts', fetchPosts)
} catch (error) {
  // 错误处理
}
// 预取查询:
await queryClient.prefetchQuery('posts', fetchPosts)

// 获取查询:
try {
  const data = await queryClient.fetchQuery('posts', fetchPosts)
} catch (error) {
  // 错误处理
}

ReactQueryErrorResetBoundaryQueryCache.resetErrorBoundaries() 已被 QueryErrorResetBoundaryuseQueryErrorResetBoundary() 取代。

它们共同提供了与以前相同的体验,但增加了选择要重置哪些组件树的控制权。有关更多信息,请参阅:

QueryCache.getQuery() 已被 QueryCache.find() 取代。

现在应使用 QueryCache.find() 从缓存中查找单个查询

QueryCache.getQueries() 已移至 QueryCache.findAll()

现在应使用 QueryCache.findAll() 从缓存中查找多个查询

QueryCache.isFetching 已移至 QueryClient.isFetching()

请注意,它现在是一个函数而不是一个属性

useQueryCache 钩子已被 useQueryClient 钩子取代。

它为其组件树返回提供的 queryClient,除了重命名之外,不需要太多调整。

查询键部分/片段不再自动传播到查询函数。

内联函数现在是向查询函数传递参数的建议方法:

tsx
// 旧版
useQuery(['post', id], (_key, id) => fetchPost(id))

// 新版
useQuery(['post', id], () => fetchPost(id))
// 旧版
useQuery(['post', id], (_key, id) => fetchPost(id))

// 新版
useQuery(['post', id], () => fetchPost(id))

如果您仍然坚持不使用内联函数,可以使用新传递的 QueryFunctionContext

tsx
useQuery(['post', id], (context) => fetchPost(context.queryKey[1]))
useQuery(['post', id], (context) => fetchPost(context.queryKey[1]))

无限查询页面参数现在通过 QueryFunctionContext.pageParam 传递

它们以前作为查询函数中的最后一个查询键参数添加,但这对于某些模式来说被证明是困难的

tsx
// 旧版
useInfiniteQuery(['posts'], (_key, pageParam = 0) => fetchPosts(pageParam))

// 新版
useInfiniteQuery(['posts'], ({ pageParam = 0 }) => fetchPosts(pageParam))
// 旧版
useInfiniteQuery(['posts'], (_key, pageParam = 0) => fetchPosts(pageParam))

// 新版
useInfiniteQuery(['posts'], ({ pageParam = 0 }) => fetchPosts(pageParam))

usePaginatedQuery() 已被删除,取而代之的是 keepPreviousData 选项

新的 keepPreviousData 选项可用于 useQueryuseInfiniteQuery,并且会对您的数据产生相同的“滞后”效果:

tsx
import { useQuery } from 'react-query'

function Page({ page }) {
  const { data } = useQuery(['page', page], fetchPage, {
    keepPreviousData: true,
  })
}
import { useQuery } from 'react-query'

function Page({ page }) {
  const { data } = useQuery(['page', page], fetchPage, {
    keepPreviousData: true,
  })
}

useInfiniteQuery() 现在是双向的

useInfiniteQuery() 接口已更改以完全支持双向无限列表。

  • options.getFetchMore 已重命名为 options.getNextPageParam
  • queryResult.canFetchMore 已重命名为 queryResult.hasNextPage
  • queryResult.fetchMore 已重命名为 queryResult.fetchNextPage
  • queryResult.isFetchingMore 已重命名为 queryResult.isFetchingNextPage
  • 添加了 options.getPreviousPageParam 选项
  • 添加了 queryResult.hasPreviousPage 属性
  • 添加了 queryResult.fetchPreviousPage 属性
  • 添加了 queryResult.isFetchingPreviousPage
  • 无限查询的 data 现在是一个包含 pages 和用于获取这些页面的 pageParams 的对象:{ pages: [data, data, data], pageParams: [...]}

单向:

tsx
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery(
    'projects',
    ({ pageParam = 0 }) => fetchProjects(pageParam),
    {
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    },
  )
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery(
    'projects',
    ({ pageParam = 0 }) => fetchProjects(pageParam),
    {
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    },
  )

双向:

tsx
const {
  data,
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
} = useInfiniteQuery(
  'projects',
  ({ pageParam = 0 }) => fetchProjects(pageParam),
  {
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
  },
)
const {
  data,
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
} = useInfiniteQuery(
  'projects',
  ({ pageParam = 0 }) => fetchProjects(pageParam),
  {
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
  },
)

单向反转:

tsx
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery(
    'projects',
    ({ pageParam = 0 }) => fetchProjects(pageParam),
    {
      select: (data) => ({
        pages: [...data.pages].reverse(),
        pageParams: [...data.pageParams].reverse(),
      }),
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    },
  )
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery(
    'projects',
    ({ pageParam = 0 }) => fetchProjects(pageParam),
    {
      select: (data) => ({
        pages: [...data.pages].reverse(),
        pageParams: [...data.pageParams].reverse(),
      }),
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    },
  )

无限查询数据现在包含用于获取这些页面的页面数组和 pageParams。

这使得更容易操作数据和页面参数,例如,删除第一页数据及其参数:

tsx
queryClient.setQueryData(['projects'], (data) => ({
  pages: data.pages.slice(1),
  pageParams: data.pageParams.slice(1),
}))
queryClient.setQueryData(['projects'], (data) => ({
  pages: data.pages.slice(1),
  pageParams: data.pageParams.slice(1),
}))

useMutation 现在返回一个对象而不是一个数组

虽然旧的方式让我们感受到了第一次发现 useState 时的温暖模糊的感觉,但它们并没有持续多久。现在变更返回的是一个单一对象。

tsx
// 旧版:
const [mutate, { status, reset }] = useMutation()

// 新版:
const { mutate, status, reset } = useMutation()
// 旧版:
const [mutate, { status, reset }] = useMutation()

// 新版:
const { mutate, status, reset } = useMutation()

mutation.mutate 不再返回 Promise

  • [mutate] 变量已更改为 mutation.mutate 函数
  • 添加了 mutation.mutateAsync 函数

我们收到了很多关于此行为的问题,因为用户希望 Promise 的行为与常规 Promise 类似。

因此,mutate 函数现在拆分为 mutatemutateAsync 函数。

mutate 函数可用于使用回调时:

tsx
const { mutate } = useMutation({ mutationFn: addTodo })

mutate('todo', {
  onSuccess: (data) => {
    console.log(data)
  },
  onError: (error) => {
    console.error(error)
  },
  onSettled: () => {
    console.log('settled')
  },
})
const { mutate } = useMutation({ mutationFn: addTodo })

mutate('todo', {
  onSuccess: (data) => {
    console.log(data)
  },
  onError: (error) => {
    console.error(error)
  },
  onSettled: () => {
    console.log('settled')
  },
})

mutateAsync 函数可用于使用 async/await 时:

tsx
const { mutateAsync } = useMutation({ mutationFn: addTodo })

try {
  const data = await mutateAsync('todo')
  console.log(data)
} catch (error) {
  console.error(error)
} finally {
  console.log('settled')
}
const { mutateAsync } = useMutation({ mutationFn: addTodo })

try {
  const data = await mutateAsync('todo')
  console.log(data)
} catch (error) {
  console.error(error)
} finally {
  console.log('settled')
}

useQuery 的对象语法现在使用折叠配置:

tsx
// 旧版:
useQuery({
  queryKey: 'posts',
  queryFn: fetchPosts,
  config: { staleTime: Infinity },
})

// 新版:
useQuery({
  queryKey: 'posts',
  queryFn: fetchPosts,
  staleTime: Infinity,
})
// 旧版:
useQuery({
  queryKey: 'posts',
  queryFn: fetchPosts,
  config: { staleTime: Infinity },
})

// 新版:
useQuery({
  queryKey: 'posts',
  queryFn: fetchPosts,
  staleTime: Infinity,
})

如果设置,QueryOptions.enabled 选项必须是布尔值 (true/false)

enabled 查询选项现在仅在值为 false 时禁用查询。 如果需要,可以使用 !!userIdBoolean(userId) 转换值,如果传递了非布尔值,则会抛出一个方便的错误。

QueryOptions.initialStale 选项已被删除

initialStale 查询选项已被删除,初始数据现在被视为常规数据。 这意味着如果提供了 initialData,则查询默认情况下会在挂载时重新获取。 如果您不想立即重新获取,可以定义一个 staleTime

QueryOptions.forceFetchOnMount 选项已被 refetchOnMount: 'always' 取代

老实说,我们积累了太多 refetchOn____ 选项,所以这应该可以清理一下。

QueryOptions.refetchOnMount 选项现在仅适用于其父组件,而不是所有查询观察者

refetchOnMount 设置为 false 时,任何其他组件都会阻止在挂载时重新获取。 在版本 3 中,只有设置了该选项的组件才不会在挂载时重新获取。

QueryOptions.queryFnParamsFilter 已被删除,取而代之的是新的 QueryFunctionContext 对象。

queryFnParamsFilter 选项已被删除,因为查询函数现在获取的是 QueryFunctionContext 对象而不是查询键。

参数仍然可以在查询函数本身内部进行过滤,因为 QueryFunctionContext 也包含查询键。

QueryOptions.notifyOnStatusChange 选项已被新的 notifyOnChangePropsnotifyOnChangePropsExclusions 选项取代。

通过这些新选项,可以在更精细的级别上配置组件何时应重新渲染。

仅在 dataerror 属性更改时重新渲染:

tsx
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    notifyOnChangeProps: ['data', 'error'],
  })
  return <div>用户名:{data.username}</div>
}
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    notifyOnChangeProps: ['data', 'error'],
  })
  return <div>用户名:{data.username}</div>
}

isStale 属性更改时阻止重新渲染:

tsx
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    notifyOnChangePropsExclusions: ['isStale'],
  })
  return <div>用户名:{data.username}</div>
}
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    notifyOnChangePropsExclusions: ['isStale'],
  })
  return <div>用户名:{data.username}</div>
}

QueryResult.clear() 函数已重命名为 QueryResult.remove()

虽然它被称为 clear,但它实际上只是从缓存中删除了查询。名称现在与功能匹配。

QueryResult.updatedAt 属性已拆分为 QueryResult.dataUpdatedAtQueryResult.errorUpdatedAt 属性

由于数据和错误可以同时存在,因此 updatedAt 属性已拆分为 dataUpdatedAterrorUpdatedAt

setConsole() 已被新的 setLogger() 函数取代

tsx
import { setLogger } from 'react-query'

// 使用 Sentry 记录
setLogger({
  error: (error) => {
    Sentry.captureException(error)
  },
})

// 使用 Winston 记录
setLogger(winston.createLogger())
import { setLogger } from 'react-query'

// 使用 Sentry 记录
setLogger({
  error: (error) => {
    Sentry.captureException(error)
  },
})

// 使用 Winston 记录
setLogger(winston.createLogger())

React Native 不再需要覆盖记录器

为了防止在查询失败时在 React Native 中显示错误屏幕,需要手动更改控制台:

tsx
import { setConsole } from 'react-query'

setConsole({
  log: console.log,
  warn: console.warn,
  error: console.warn,
})
import { setConsole } from 'react-query'

setConsole({
  log: console.log,
  warn: console.warn,
  error: console.warn,
})

在版本 3 中,当 React Query 在 React Native 中使用时,这是自动完成的

Typescript

QueryStatus 已从枚举更改为联合类型

因此,如果您以前根据 QueryStatus 枚举属性检查查询或变更的状态属性,现在需要根据枚举先前为每个属性保留的字符串字面量进行检查。

因此,您必须将枚举属性更改为其等效的字符串字面量,如下所示:

  • QueryStatus.Idle -> 'idle'
  • QueryStatus.Loading -> 'loading'
  • QueryStatus.Error -> 'error'
  • QueryStatus.Success -> 'success'

以下是您需要进行的更改示例:

tsx
- import { useQuery, QueryStatus } from 'react-query'; // [!code --]
+ import { useQuery } from 'react-query'; // [!code ++]

const { data, status } = useQuery(['post', id], () => fetchPost(id))

- if (status === QueryStatus.Loading) { // [!code --]
+ if (status === 'loading') { // [!code ++]
  ...
}

- if (status === QueryStatus.Error) { // [!code --]
+ if (status === 'error') { // [!code ++]
  ...
}
- import { useQuery, QueryStatus } from 'react-query'; // [!code --]
+ import { useQuery } from 'react-query'; // [!code ++]

const { data, status } = useQuery(['post', id], () => fetchPost(id))

- if (status === QueryStatus.Loading) { // [!code --]
+ if (status === 'loading') { // [!code ++]
  ...
}

- if (status === QueryStatus.Error) { // [!code --]
+ if (status === 'error') { // [!code ++]
  ...
}

新功能

查询数据选择器

useQueryuseInfiniteQuery 钩子现在有一个 select 选项,用于选择或转换查询结果的某些部分。

tsx
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    select: (user) => user.username,
  })
  return <div>用户名:{data}</div>
}
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    select: (user) => user.username,
  })
  return <div>用户名:{data}</div>
}

notifyOnChangeProps 选项设置为 ['data', 'error'] 以仅在所选数据更改时重新渲染。

useQueries() 钩子,用于可变长度并行查询执行

希望您可以在循环中运行 useQuery 吗?钩子规则不允许,但使用新的 useQueries() 钩子,您可以!

tsx
import { useQueries } from 'react-query'

function Overview() {
  const results = useQueries([
    { queryKey: ['post', 1], queryFn: fetchPost },
    { queryKey: ['post', 2], queryFn: fetchPost },
  ])
  return (
    <ul>
      {results.map(({ data }) => data && <li key={data.id}>{data.title})</li>)}
    </ul>
  )
}
import { useQueries } from 'react-query'

function Overview() {
  const results = useQueries([
    { queryKey: ['post', 1], queryFn: fetchPost },
    { queryKey: ['post', 2], queryFn: fetchPost },
  ])
  return (
    <ul>
      {results.map(({ data }) => data && <li key={data.id}>{data.title})</li>)}
    </ul>
  )
}

重试/离线变更

默认情况下,React Query 不会因错误重试变更,但可以使用 retry 选项:

tsx
const mutation = useMutation({
  mutationFn: addTodo,
  retry: 3,
})
const mutation = useMutation({
  mutationFn: addTodo,
  retry: 3,
})

如果由于设备离线而导致变更失败,则在设备重新连接时将以相同的顺序重试它们。

持久化变更

现在可以将变更持久化到存储并在稍后恢复。有关更多信息,请参见变更文档。

QueryObserver

QueryObserver 可用于创建和/或监视查询:

tsx
const observer = new QueryObserver(queryClient, { queryKey: 'posts' })

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})
const observer = new QueryObserver(queryClient, { queryKey: 'posts' })

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})

InfiniteQueryObserver

InfiniteQueryObserver 可用于创建和/或监视无限查询:

tsx
const observer = new InfiniteQueryObserver(queryClient, {
  queryKey: 'posts',
  queryFn: fetchPosts,
  getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})
const observer = new InfiniteQueryObserver(queryClient, {
  queryKey: 'posts',
  queryFn: fetchPosts,
  getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})

QueriesObserver

QueriesObserver 可用于创建和/或监视多个查询:

tsx
const observer = new QueriesObserver(queryClient, [
  { queryKey: ['post', 1], queryFn: fetchPost },
  { queryKey: ['post', 2], queryFn: fetchPost },
])

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})
const observer = new QueriesObserver(queryClient, [
  { queryKey: ['post', 1], queryFn: fetchPost },
  { queryKey: ['post', 2], queryFn: fetchPost },
])

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})

设置特定查询的默认选项

QueryClient.setQueryDefaults() 方法可用于设置特定查询的默认选项:

tsx
queryClient.setQueryDefaults(['posts'], { queryFn: fetchPosts })

function Component() {
  const { data } = useQuery(['posts'])
}
queryClient.setQueryDefaults(['posts'], { queryFn: fetchPosts })

function Component() {
  const { data } = useQuery(['posts'])
}

设置特定变更的默认选项

QueryClient.setMutationDefaults() 方法可用于设置特定变更的默认选项:

tsx
queryClient.setMutationDefaults(['addPost'], { mutationFn: addPost })

function Component() {
  const { mutate } = useMutation({ mutationKey: ['addPost'] })
}
queryClient.setMutationDefaults(['addPost'], { mutationFn: addPost })

function Component() {
  const { mutate } = useMutation({ mutationKey: ['addPost'] })
}

useIsFetching()

useIsFetching() 钩子现在接受过滤器,例如,可用于仅为某些类型的查询显示微调器:

tsx
const fetches = useIsFetching({ queryKey: ['posts'] })
const fetches = useIsFetching({ queryKey: ['posts'] })

核心分离

React Query 的核心现在与 React 完全分离,这意味着它也可以独立使用或在其他框架中使用。使用 react-query/core 入口点仅导入核心功能:

tsx
import { QueryClient } from 'react-query/core'
import { QueryClient } from 'react-query/core'

开发者工具现在是主仓库和 npm 包的一部分

开发者工具现在包含在 react-query 包本身的 react-query/devtools 导入下。只需将 react-query-devtools 导入替换为 react-query/devtools 即可