迁移到 TanStack Query v5

重大更改

v5 是一个主要版本,因此有一些需要注意的重大更改

支持单一签名,一个对象

useQuery 及其朋友过去在 TypeScript 中有很多重载 - 函数可以被调用的不同方式。这不仅在类型方面难以维护,而且还需要运行时检查来查看第一个和第二个参数的类型,以正确创建选项。

现在我们只支持对象格式。

tsx
useQuery(key, fn, options) // [!code --]
useQuery({ queryKey, queryFn, ...options }) // [!code ++]
useInfiniteQuery(key, fn, options) // [!code --]
useInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
useMutation(fn, options) // [!code --]
useMutation({ mutationFn, ...options }) // [!code ++]
useIsFetching(key, filters) // [!code --]
useIsFetching({ queryKey, ...filters }) // [!code ++]
useIsMutating(key, filters) // [!code --]
useIsMutating({ mutationKey, ...filters }) // [!code ++]
useQuery(key, fn, options) // [!code --]
useQuery({ queryKey, queryFn, ...options }) // [!code ++]
useInfiniteQuery(key, fn, options) // [!code --]
useInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
useMutation(fn, options) // [!code --]
useMutation({ mutationFn, ...options }) // [!code ++]
useIsFetching(key, filters) // [!code --]
useIsFetching({ queryKey, ...filters }) // [!code ++]
useIsMutating(key, filters) // [!code --]
useIsMutating({ mutationKey, ...filters }) // [!code ++]
tsx
queryClient.isFetching(key, filters) // [!code --]
queryClient.isFetching({ queryKey, ...filters }) // [!code ++]
queryClient.ensureQueryData(key, filters) // [!code --]
queryClient.ensureQueryData({ queryKey, ...filters }) // [!code ++]
queryClient.getQueriesData(key, filters) // [!code --]
queryClient.getQueriesData({ queryKey, ...filters }) // [!code ++]
queryClient.setQueriesData(key, updater, filters, options) // [!code --]
queryClient.setQueriesData({ queryKey, ...filters }, updater, options) // [!code ++]
queryClient.removeQueries(key, filters) // [!code --]
queryClient.removeQueries({ queryKey, ...filters }) // [!code ++]
queryClient.resetQueries(key, filters, options) // [!code --]
queryClient.resetQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.cancelQueries(key, filters, options) // [!code --]
queryClient.cancelQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.invalidateQueries(key, filters, options) // [!code --]
queryClient.invalidateQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.refetchQueries(key, filters, options) // [!code --]
queryClient.refetchQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.fetchQuery(key, fn, options) // [!code --]
queryClient.fetchQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.prefetchQuery(key, fn, options) // [!code --]
queryClient.prefetchQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.fetchInfiniteQuery(key, fn, options) // [!code --]
queryClient.fetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.prefetchInfiniteQuery(key, fn, options) // [!code --]
queryClient.prefetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.isFetching(key, filters) // [!code --]
queryClient.isFetching({ queryKey, ...filters }) // [!code ++]
queryClient.ensureQueryData(key, filters) // [!code --]
queryClient.ensureQueryData({ queryKey, ...filters }) // [!code ++]
queryClient.getQueriesData(key, filters) // [!code --]
queryClient.getQueriesData({ queryKey, ...filters }) // [!code ++]
queryClient.setQueriesData(key, updater, filters, options) // [!code --]
queryClient.setQueriesData({ queryKey, ...filters }, updater, options) // [!code ++]
queryClient.removeQueries(key, filters) // [!code --]
queryClient.removeQueries({ queryKey, ...filters }) // [!code ++]
queryClient.resetQueries(key, filters, options) // [!code --]
queryClient.resetQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.cancelQueries(key, filters, options) // [!code --]
queryClient.cancelQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.invalidateQueries(key, filters, options) // [!code --]
queryClient.invalidateQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.refetchQueries(key, filters, options) // [!code --]
queryClient.refetchQueries({ queryKey, ...filters }, options) // [!code ++]
queryClient.fetchQuery(key, fn, options) // [!code --]
queryClient.fetchQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.prefetchQuery(key, fn, options) // [!code --]
queryClient.prefetchQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.fetchInfiniteQuery(key, fn, options) // [!code --]
queryClient.fetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
queryClient.prefetchInfiniteQuery(key, fn, options) // [!code --]
queryClient.prefetchInfiniteQuery({ queryKey, queryFn, ...options }) // [!code ++]
tsx
queryCache.find(key, filters) // [!code --]
queryCache.find({ queryKey, ...filters }) // [!code ++]
queryCache.findAll(key, filters) // [!code --]
queryCache.findAll({ queryKey, ...filters }) // [!code ++]
queryCache.find(key, filters) // [!code --]
queryCache.find({ queryKey, ...filters }) // [!code ++]
queryCache.findAll(key, filters) // [!code --]
queryCache.findAll({ queryKey, ...filters }) // [!code ++]

queryClient.getQueryData 现在仅接受 queryKey 作为参数

queryClient.getQueryData 参数已更改为仅接受 queryKey

tsx
queryClient.getQueryData(queryKey, filters) // [!code --]
queryClient.getQueryData(queryKey) // [!code ++]
queryClient.getQueryData(queryKey, filters) // [!code --]
queryClient.getQueryData(queryKey) // [!code ++]

queryClient.getQueryState 现在仅接受 queryKey 作为参数

queryClient.getQueryState 参数已更改为仅接受 queryKey

tsx
queryClient.getQueryState(queryKey, filters) // [!code --]
queryClient.getQueryState(queryKey) // [!code ++]
queryClient.getQueryState(queryKey, filters) // [!code --]
queryClient.getQueryState(queryKey) // [!code ++]

Codemod

为了使移除重载的迁移更容易,v5 附带了一个 codemod。

codemod 是尽最大努力帮助你迁移重大更改的尝试。请彻底审查生成的代码!此外,还有一些边缘情况 codemod 无法找到,因此请密切关注日志输出。

如果你想针对 .js.jsx 文件运行它,请使用以下命令

npx jscodeshift@latest ./path/to/src/ \
  --extensions=js,jsx \
  --transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.cjs
npx jscodeshift@latest ./path/to/src/ \
  --extensions=js,jsx \
  --transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.cjs

如果你想针对 .ts.tsx 文件运行它,请使用以下命令

npx jscodeshift@latest ./path/to/src/ \
  --extensions=ts,tsx \
  --parser=tsx \
  --transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.cjs
npx jscodeshift@latest ./path/to/src/ \
  --extensions=ts,tsx \
  --parser=tsx \
  --transform=./node_modules/@tanstack/react-query/build/codemods/src/v5/remove-overloads/remove-overloads.cjs

请注意,在 TypeScript 的情况下,你需要使用 tsx 作为解析器;否则,codemod 将无法正确应用!

注意: 应用 codemod 可能会破坏你的代码格式,因此请不要忘记在应用 codemod 后运行 prettier 和/或 eslint

关于 codemod 如何工作的一些说明

  • 通常,我们正在寻找幸运的情况,即当第一个参数是对象表达式并且包含 "queryKey" 或 "mutationKey" 属性(取决于正在转换的 hook/方法调用)。如果是这种情况,你的代码已经匹配了新签名,因此 codemod 不会触及它。🎉
  • 如果未满足上述条件,则 codemod 将检查第一个参数是否是数组表达式或引用数组表达式的标识符。如果是这种情况,codemod 会将其放入对象表达式中,然后它将成为第一个参数。
  • 如果可以推断对象参数,则 codemod 将尝试将已存在的属性复制到新创建的属性中。
  • 如果 codemod 无法推断用法,则它将在控制台上留下消息。该消息包含文件名和用法的行号。在这种情况下,你需要手动进行迁移。
  • 如果转换导致错误,你也会在控制台上看到一条消息。此消息将通知你发生了意外情况,请手动进行迁移。

useQuery(和 QueryObserver)上的回调已被移除

onSuccessonErroronSettled 已从 Queries 中移除。它们尚未触及 Mutations。请参阅 此 RFC,了解此更改背后的动机以及替代方案。

refetchInterval 回调函数仅接收 query 参数

这简化了回调的调用方式(refetchOnWindowFocusrefetchOnMountrefetchOnReconnect 回调也都只接收 query 参数),并且修复了当回调获取被 select 转换的数据时的一些类型问题。

tsx
- refetchInterval: number | false | ((data: TData | undefined, query: Query) => number | false | undefined) // [!code --]
+ refetchInterval: number | false | ((query: Query) => number | false | undefined) // [!code ++]
- refetchInterval: number | false | ((data: TData | undefined, query: Query) => number | false | undefined) // [!code --]
+ refetchInterval: number | false | ((query: Query) => number | false | undefined) // [!code ++]

你仍然可以使用 query.state.data 访问数据,但是,它将不是已被 select 转换的数据。如果你需要访问转换后的数据,你可以对 query.state.data 再次调用转换。

remove 方法已从 useQuery 中移除

以前,remove 方法用于从 queryCache 中删除查询,而无需通知观察者。它最适合用于命令式地删除不再需要的数据,例如,当用户注销时。

但是,当查询仍然处于活动状态时执行此操作没有多大意义,因为它只会触发下一个重新渲染的硬加载状态。

如果你仍然需要删除查询,可以使用 queryClient.removeQueries({queryKey: key})

tsx
const queryClient = useQueryClient()
const query = useQuery({ queryKey, queryFn })

query.remove() // [!code --]
queryClient.removeQueries({ queryKey }) // [!code ++]
const queryClient = useQueryClient()
const query = useQuery({ queryKey, queryFn })

query.remove() // [!code --]
queryClient.removeQueries({ queryKey }) // [!code ++]

最低要求的 TypeScript 版本现在是 4.7

主要是因为围绕类型推断发布了一个重要的修复程序。有关更多信息,请参阅此 TypeScript 问题

isDataEqual 选项已从 useQuery 中移除

以前,此函数用于指示是否使用先前的 data (true) 或新数据 (false) 作为查询的已解析数据。

你可以通过将函数传递给 structuralSharing 来实现相同的功能

tsx
 import { replaceEqualDeep } from '@tanstack/react-query'

- isDataEqual: (oldData, newData) => customCheck(oldData, newData) // [!code --]
+ structuralSharing: (oldData, newData) => customCheck(oldData, newData) ? oldData : replaceEqualDeep(oldData, newData) // [!code ++]
 import { replaceEqualDeep } from '@tanstack/react-query'

- isDataEqual: (oldData, newData) => customCheck(oldData, newData) // [!code --]
+ structuralSharing: (oldData, newData) => customCheck(oldData, newData) ? oldData : replaceEqualDeep(oldData, newData) // [!code ++]

已弃用的自定义 logger 已被移除

自定义 logger 在版本 4 中已被弃用,并在本版本中已移除。日志记录仅在开发模式下有效,在开发模式下,无需传递自定义 logger。

支持的浏览器

我们已更新了 browserslist 以生成更现代、性能更高且更小的 bundle。你可以在 此处 阅读有关要求的更多信息。

私有类字段和方法

TanStack Query 一直在类上具有私有字段和方法,但它们并不是真正的私有 - 它们只是在 TypeScript 中是私有的。我们现在使用 ECMAScript 私有类特性,这意味着这些字段现在是真正私有的,并且在运行时无法从外部访问。

cacheTime 重命名为 gcTime

几乎每个人都误解了 cacheTime。它听起来像是“数据被缓存的时间量”,但这是不正确的。

cacheTime 只要查询仍在使用, 就不会执行任何操作。它仅在查询不再使用时才开始生效。时间过去后,数据将被“垃圾回收”,以避免缓存增长。

gc 指的是“垃圾回收”时间。它有点技术性,但也是计算机科学中一个相当 广为人知的缩写

tsx
const MINUTE = 1000 * 60;

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
-      cacheTime: 10 * MINUTE, // [!code --]
+      gcTime: 10 * MINUTE, // [!code ++]
    },
  },
})
const MINUTE = 1000 * 60;

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
-      cacheTime: 10 * MINUTE, // [!code --]
+      gcTime: 10 * MINUTE, // [!code ++]
    },
  },
})

useErrorBoundary 选项已重命名为 throwOnError

为了使 useErrorBoundary 选项更具框架无关性,并避免与已建立的 React 函数前缀“use” (用于 hooks) 和 “ErrorBoundary” 组件名称混淆,它已被重命名为 throwOnError ,以更准确地反映其功能。

TypeScript: Error 现在是错误的默认类型,而不是 unknown

即使在 JavaScript 中,你可以 throw 任何东西(这使得 unknown 是最正确的类型),但几乎总是会抛出 Errors (或 Error 的子类)。此更改使得在大多数情况下在 TypeScript 中使用 error 字段更容易。

如果你想抛出非 Error 的东西,你现在必须自己设置泛型

ts
useQuery<number, string>({
  queryKey: ['some-query'],
  queryFn: async () => {
    if (Math.random() > 0.5) {
      throw 'some error'
    }
    return 42
  },
})
useQuery<number, string>({
  queryKey: ['some-query'],
  queryFn: async () => {
    if (Math.random() > 0.5) {
      throw 'some error'
    }
    return 42
  },
})

有关全局设置不同类型 Error 的方法,请参阅 TypeScript 指南

eslint prefer-query-object-syntax 规则已移除

由于现在唯一支持的语法是对象语法,因此不再需要此规则

移除 keepPreviousData,转而使用 placeholderData 恒等函数

我们已移除 keepPreviousData 选项和 isPreviousData 标志,因为它们的作用与 placeholderDataisPlaceholderData 标志大致相同。

为了实现与 keepPreviousData 相同的功能,我们已将之前的查询 data 作为参数添加到 placeholderData 中,它接受一个恒等函数。因此,你只需向 placeholderData 提供一个恒等函数,或使用 Tanstack Query 中包含的 keepPreviousData 函数。

这里需要注意的是,useQueriesplaceholderData 函数中不会接收 previousData 作为参数。这是因为传递到数组中的查询具有动态性质,这可能会导致占位符和 queryFn 的结果形状不同。

tsx
import {
   useQuery,
+  keepPreviousData // [!code ++]
} from "@tanstack/react-query";

const {
   data,
-  isPreviousData, // [!code --]
+  isPlaceholderData, // [!code ++]
} = useQuery({
  queryKey,
  queryFn,
- keepPreviousData: true, // [!code --]
+ placeholderData: keepPreviousData // [!code ++]
});
import {
   useQuery,
+  keepPreviousData // [!code ++]
} from "@tanstack/react-query";

const {
   data,
-  isPreviousData, // [!code --]
+  isPlaceholderData, // [!code ++]
} = useQuery({
  queryKey,
  queryFn,
- keepPreviousData: true, // [!code --]
+ placeholderData: keepPreviousData // [!code ++]
});

恒等函数,在 Tanstack Query 的上下文中,指的是始终返回其提供的参数(即数据)不变的函数。

ts
useQuery({
  queryKey,
  queryFn,
  placeholderData: (previousData, previousQuery) => previousData, // identity function with the same behaviour as `keepPreviousData`
})
useQuery({
  queryKey,
  queryFn,
  placeholderData: (previousData, previousQuery) => previousData, // identity function with the same behaviour as `keepPreviousData`
})

但是,此更改有一些注意事项,你必须注意

  • placeholderData 将始终使你进入 success 状态,而 keepPreviousData 会为你提供先前查询的状态。如果我们在成功获取数据后又收到后台重新获取错误,则该状态可能是 error 。但是,错误本身并未共享,因此我们决定坚持 placeholderData 的行为。

  • keepPreviousData 为你提供了先前数据的 dataUpdatedAt 时间戳,而使用 placeholderData 时, dataUpdatedAt 将保持在 0 。如果你想在屏幕上持续显示该时间戳,这可能会很烦人。但是,你可以通过 useEffect 来解决。

    ts
    const [updatedAt, setUpdatedAt] = useState(0)
    
    const { data, dataUpdatedAt } = useQuery({
      queryKey: ['projects', page],
      queryFn: () => fetchProjects(page),
    })
    
    useEffect(() => {
      if (dataUpdatedAt > updatedAt) {
        setUpdatedAt(dataUpdatedAt)
      }
    }, [dataUpdatedAt])
    
    const [updatedAt, setUpdatedAt] = useState(0)
    
    const { data, dataUpdatedAt } = useQuery({
      queryKey: ['projects', page],
      queryFn: () => fetchProjects(page),
    })
    
    useEffect(() => {
      if (dataUpdatedAt > updatedAt) {
        setUpdatedAt(dataUpdatedAt)
      }
    }, [dataUpdatedAt])
    

窗口焦点重新获取不再监听 focus 事件

现在专门使用 visibilitychange 事件。这是可能的,因为我们只支持支持 visibilitychange 事件的浏览器。这修复了一系列问题 如此处列出

网络状态不再依赖于 navigator.onLine 属性

navigator.onLine 在基于 Chromium 的浏览器中效果不佳。存在许多关于误报的 问题,这导致 Queries 被错误地标记为 offline

为了规避这个问题,我们现在始终以 online: true 开始,并且仅监听 onlineoffline 事件以更新状态。

这应该会降低误报的可能性,但是,对于通过 serviceWorkers 加载的离线应用程序,这可能意味着误报,即使没有互联网连接,这些应用程序也可以工作。

移除自定义 context prop,转而使用自定义 queryClient 实例

在 v4 中,我们引入了将自定义 context 传递给所有 react-query hooks 的可能性。这允许在使用 MicroFrontends 时进行适当的隔离。

但是, context 是一个仅限 react 的功能。 context 所做的只是让我们访问 queryClient 。我们可以通过允许直接传入自定义 queryClient 来实现相同的隔离。反过来,这将使其他框架能够以框架无关的方式拥有相同的功能。

tsx
import { queryClient } from './my-client'

const { data } = useQuery(
  {
    queryKey: ['users', id],
    queryFn: () => fetch(...),
-   context: customContext // [!code --]
  },
+  queryClient, // [!code ++]
)
import { queryClient } from './my-client'

const { data } = useQuery(
  {
    queryKey: ['users', id],
    queryFn: () => fetch(...),
-   context: customContext // [!code --]
  },
+  queryClient, // [!code ++]
)

移除 refetchPage,转而使用 maxPages

在 v4 中,我们引入了使用 refetchPage 函数定义无限查询要重新获取的页面的可能性。

但是,重新获取所有页面可能会导致 UI 不一致。此外,此选项在例如 queryClient.refetchQueries 上可用,但它仅对无限查询有效,而对“普通”查询无效。

v5 包含一个新的 maxPages 选项,用于无限查询,以限制要存储在查询数据中和重新获取的页数。这项新功能处理了最初为 refetchPage 页面功能确定的用例,而没有相关问题。

新的 dehydrate API

你可以传递给 dehydrate 的选项已得到简化。Queries 和 Mutations 始终会被脱水(根据默认函数实现)。要更改此行为,你可以实现函数等效项 shouldDehydrateQueryshouldDehydrateMutation 来代替已移除的布尔选项 dehydrateMutationsdehydrateQueries 。要获得完全不水合查询/突变的旧行为,请传入 () => false

tsx
- dehydrateMutations?: boolean // [!code --]
- dehydrateQueries?: boolean // [!code --]
- dehydrateMutations?: boolean // [!code --]
- dehydrateQueries?: boolean // [!code --]

无限查询现在需要一个 initialPageParam

之前,我们已将 undefined 传递给 queryFn 作为 pageParam ,你可以在 queryFn 函数签名中为 pageParam 参数分配默认值。这样做有一个缺点,即将 undefined 存储在 queryCache 中,这是不可序列化的。

相反,你现在必须将显式的 initialPageParam 传递给无限查询选项。这将用作第一页的 pageParam

tsx
useInfiniteQuery({
   queryKey,
-  queryFn: ({ pageParam = 0 }) => fetchSomething(pageParam), // [!code --]
+  queryFn: ({ pageParam }) => fetchSomething(pageParam), // [!code ++]
+  initialPageParam: 0, // [!code ++]
   getNextPageParam: (lastPage) => lastPage.next,
})
useInfiniteQuery({
   queryKey,
-  queryFn: ({ pageParam = 0 }) => fetchSomething(pageParam), // [!code --]
+  queryFn: ({ pageParam }) => fetchSomething(pageParam), // [!code ++]
+  initialPageParam: 0, // [!code ++]
   getNextPageParam: (lastPage) => lastPage.next,
})

无限查询的手动模式已被移除

以前,我们允许通过将 pageParam 值直接传递给 fetchNextPagefetchPreviousPage 来覆盖将从 getNextPageParamgetPreviousPageParam 返回的 pageParams 。此功能完全不适用于重新获取,并且未被广泛知晓或使用。这也意味着现在无限查询需要 getNextPageParam

getNextPageParamgetPreviousPageParam 返回 null 现在表示没有更多页面可用

在 v4 中,你需要显式返回 undefined 以指示没有更多页面可用。我们已扩大此检查范围以包括 null

服务器上不进行重试

在服务器上, retry 现在默认为 0 而不是 3 。对于预取,我们始终默认为 0 次重试,但是由于启用了 suspense 的查询现在也可以直接在服务器上执行(自 React18 起),因此我们必须确保我们根本不在服务器上重试。

status: loading 已更改为 status: pendingisLoading 已更改为 isPending ,并且 isInitialLoading 现在已重命名为 isLoading

状态 loading 已被重命名为 pending,类似地,派生的 isLoading 标志已被重命名为 isPending

对于 mutations 也是如此,status 已从 loading 更改为 pending,并且 isLoading 标志已更改为 isPending

最后,一个新的派生的 isLoading 标志已添加到查询中,它被实现为 isPending && isFetching。这意味着 isLoadingisInitialLoading 具有相同的作用,但 isInitialLoading 现在已被弃用,将在下一个主要版本中删除。

要了解此更改背后的原因,请查看 v5 路线图讨论

hashQueryKey 已重命名为 hashKey

因为它也哈希 mutation 键,并且可以在 useIsMutatinguseMutationStatepredicate 函数内部使用,这些函数会传递 mutations。

Vue Query 破坏性更改

useQueries 组合式函数返回 ref 而不是 reactive

为了修复与 Vue 2 的兼容性,useQueries 组合式函数现在返回包装在 ref 中的 queries 数组。之前返回的是 reactive,这导致了多个问题

  • 用户可以展开返回值,从而失去响应式。
  • 用于返回值的 readonly 包装器破坏了 Vue 2 响应式检测机制。这在 Vue 2.6 中是一个潜在问题,但在 Vue 2.7 中表现为错误。
  • Vue 2 不支持数组作为 reactive 的根值。

通过此更改,所有这些问题都已修复。

此外,这使 useQueries 与其他将所有值作为 refs 返回的组合式函数对齐。

现在需要 Vue v3.3

为了能够提供 Vue 后续版本的新功能,我们现在要求 Vue 3 至少为 v3.3 版本。Vue 2.x 的要求保持不变。

新功能 🚀

v5 还带来了新功能

简化的乐观更新

我们有一种新的、简化的方法来执行乐观更新,通过利用从 useMutation 返回的 variables

tsx
const queryInfo = useTodos()
const addTodoMutation = useMutation({
  mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
  onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})

if (queryInfo.data) {
  return (
    <ul>
      {queryInfo.data.items.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
      {addTodoMutation.isPending && (
        <li key={String(addTodoMutation.submittedAt)} style={{ opacity: 0.5 }}>
          {addTodoMutation.variables}
        </li>
      )}
    </ul>
  )
}
const queryInfo = useTodos()
const addTodoMutation = useMutation({
  mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
  onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
})

if (queryInfo.data) {
  return (
    <ul>
      {queryInfo.data.items.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
      {addTodoMutation.isPending && (
        <li key={String(addTodoMutation.submittedAt)} style={{ opacity: 0.5 }}>
          {addTodoMutation.variables}
        </li>
      )}
    </ul>
  )
}

在这里,我们仅更改 mutation 运行时 UI 的外观,而不是直接将数据写入缓存。如果我们只需要在一个地方显示乐观更新,这效果最佳。有关更多详情,请查看乐观更新文档

有限的无限查询,带有新的 maxPages 选项

当需要无限滚动或分页时,无限查询非常有用。但是,您获取的页面越多,消耗的内存就越多,这也会减慢查询重新获取过程,因为所有页面都按顺序重新获取。

版本 5 为无限查询提供了一个新的 maxPages 选项,允许开发人员限制存储在查询数据中并随后重新获取的页面数量。您可以根据您想要提供的 UX 和重新获取性能调整 maxPages 值。

请注意,无限列表必须是双向的,这需要定义 getNextPageParamgetPreviousPageParam

无限查询可以预取多个页面

无限查询可以像常规查询一样预取。默认情况下,只会预取查询的第一页,并将存储在给定的 QueryKey 下。如果您想要预取多页,可以使用 pages 选项。阅读预取指南以获取更多信息。

用于 useQueries 的新 combine 选项

有关更多详情,请参阅 useQueries 文档

实验性的 fine grained storage persister

有关更多详情,请参阅 experimental_createPersister 文档

injectionContext 中运行 vue-query 组合式函数的能力

以前,vue-query 组合式函数只能在组件的 setup 函数中运行。
我们有一个应急方案,如果用户提供 queryClient 作为组合式函数选项,则允许在任何地方运行这些钩子。

现在,您可以在任何支持 injectionContext 的函数中使用 vue-query 组合式函数。例如:路由器导航守卫。当使用此新功能时,请确保 vue-query 组合式函数在 effectScope 中运行。否则可能导致内存泄漏。我们添加了 dev-only 警告,用于告知用户潜在的误用。