queryClient.getQueryData 现在仅接受 queryKey 作为参数queryClient.getQueryState 现在仅接受 queryKey 作为参数refetchInterval 回调函数仅传递 queryremove 方法已从 useQuery 中删除isDataEqual 选项已从 useQuery 中删除cacheTime 重命名为 gcTimeuseErrorBoundary 选项已重命名为 throwOnErrorError 现在是错误的默认类型,而不是 unknownprefer-query-object-syntax 规则已删除keepPreviousData,取而代之的是 placeholderData 标识函数focus 事件navigator.onLine 属性context 属性,取而代之的是自定义 queryClient 实例refetchPage,取而代之的是 maxPagesdehydrate APIinitialPageParamgetNextPageParam 或 getPreviousPageParam 返回 null 现在表示没有更多可用页面status: loading 已更改为 status: pending,isLoading 已更改为 isPending,isInitialLoading 现已重命名为 isLoadinghashQueryKey 已重命名为 hashKeyQueryClientProvider 中的 contextSharing 属性已被删除unstable_batchedUpdates 作为批处理函数useQueries 的新 combine 选项细粒度存储持久化程序v5 是一个主要版本,因此需要注意一些重大更改:
useQuery 及其相关函数以前在 TypeScript 中有许多重载:可以调用该函数的不同方式。这不仅在类型方面难以维护,而且还需要在运行时检查第一个和第二个参数的类型,以正确创建选项。
现在我们只支持对象格式。
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 ++]
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 ++]
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, filters) // [!code --]
queryClient.getQueryData(queryKey) // [!code ++]
queryClient.getQueryData(queryKey, filters) // [!code --]
queryClient.getQueryData(queryKey) // [!code ++]
queryClient.getQueryState 参数已更改为仅接受 queryKey
queryClient.getQueryState(queryKey, filters) // [!code --]
queryClient.getQueryState(queryKey) // [!code ++]
queryClient.getQueryState(queryKey, filters) // [!code --]
queryClient.getQueryState(queryKey) // [!code ++]
为了简化删除重载的迁移,v5 附带了一个代码转换工具。
代码转换工具会尽力帮助您迁移重大更改。请仔细检查生成的代码!此外,代码转换工具无法找到某些边缘情况,因此请密切关注日志输出。
如果要针对 .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 作为解析器;否则,代码转换工具将无法正确应用!
**注意:**应用代码转换工具可能会破坏您的代码格式,因此请不要忘记在应用代码转换工具后运行 prettier 和/或 eslint!
关于代码转换工具如何工作的一些说明:
onSuccess、onError 和 onSettled 已从查询中删除。它们未对变更进行修改。有关此更改背后的动机以及应采取的措施,请参阅此 RFC。
这简化了回调的调用方式(refetchOnWindowFocus、refetchOnMount 和 refetchOnReconnect 回调也都只传递查询),并且修复了一些当回调获取由 select 转换的数据时的类型问题。
- 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 方法用于从 queryCache 中删除查询,而不会通知观察者。它最适合用于命令式地删除不再需要的数据,例如,在注销用户时。
但是,当查询仍处于活动状态时执行此操作没有多大意义,因为它只会在下一次重新渲染时触发硬加载状态。
如果您仍然需要删除查询,可以使用 queryClient.removeQueries({queryKey: key})
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 问题。
以前,此函数用于指示是将先前 data (true) 还是新数据 (false) 用作查询的已解析数据。
您可以通过向 structuralSharing 传递一个函数来实现相同的功能:
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 ++]
自定义记录器在版本 4 中已被弃用,并在此版本中被删除。日志记录仅在开发模式下有效,在这种模式下不需要传递自定义记录器。
我们更新了我们的 browserslist 以生成更现代、性能更好且更小的捆绑包。您可以在此处阅读有关要求的信息。
TanStack Query 始终在类上具有私有字段和方法,但它们并非真正的私有——它们只是在 TypeScript 中是私有的。我们现在使用 ECMAScript 私有类特性,这意味着这些字段现在是真正的私有字段,并且无法在运行时从外部访问。
几乎每个人都弄错了 cacheTime。它听起来像是“数据缓存的时间量”,但这是不正确的。
只要查询仍在使用中,cacheTime 就不会执行任何操作。它仅在查询变为未使用状态后才会生效。时间过后,数据将被“垃圾回收”以避免缓存增长。
gc 指的是“垃圾回收”时间。它有点技术性,但在计算机科学中也是一个相当众所周知的缩写。
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 选项更具框架无关性,并避免与已建立的 React 函数前缀“use”(用于钩子)和“ErrorBoundary”组件名称混淆,它已重命名为 throwOnError 以更准确地反映其功能。
尽管在 JavaScript 中,您可以 throw 任何东西(这使得 unknown 成为最正确的类型),但几乎总是抛出 Errors(或 Error 的子类)。此更改使得在大多数情况下更容易在 TypeScript 中使用 error 字段。
如果您想抛出非 Error 的东西,现在必须自己设置泛型:
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
},
})
有关如何全局设置不同类型错误的方法,请参阅 TypeScript 指南。
由于现在唯一支持的语法是对象语法,因此不再需要此规则
我们删除了 keepPreviousData 选项和 isPreviousData 标志,因为它们的功能与 placeholderData 和 isPlaceholderData 标志基本相同。
为了实现与 keepPreviousData 相同的功能,我们将先前的查询 data 作为参数添加到了 placeholderData 中,该参数接受一个标识函数。因此,您只需要向 placeholderData 提供一个标识函数,或者使用 Tanstack Query 中包含的 keepPreviousData 函数。
此处需要注意的一点是,useQueries 不会在 placeholderData 函数中接收 previousData 作为参数。这是由于数组中传递的查询的动态特性,这可能导致占位符和 queryFn 的结果形状不同。
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 的上下文中,标识函数是指始终返回其提供的参数(即数据)不变的函数。
useQuery({
queryKey,
queryFn,
placeholderData: (previousData, previousQuery) => previousData, // 与 `keepPreviousData` 行为相同的标识函数
})
useQuery({
queryKey,
queryFn,
placeholderData: (previousData, previousQuery) => previousData, // 与 `keepPreviousData` 行为相同的标识函数
})
但是,此更改存在一些需要注意的警告:
placeholderData 始终会将您置于 success 状态,而 keepPreviousData 会为您提供先前查询的状态。如果数据已成功获取然后出现后台重新获取错误,则该状态可能为 error。但是,错误本身并未共享,因此我们决定坚持使用 placeholderData 的行为。
keepPreviousData 为您提供了先前数据的 dataUpdatedAt 时间戳,而使用 placeholderData 时,dataUpdatedAt 将保持为 0。如果您想在屏幕上连续显示该时间戳,这可能会很烦人。但是,您可以使用 useEffect 来解决此问题。
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])
现在仅使用 visibilitychange 事件。这是可能的,因为我们仅支持支持 visibilitychange 事件的浏览器。这修复了此处列出的许多问题。
navigator.onLine 在基于 Chromium 的浏览器中效果不佳。存在许多问题围绕假阴性,这导致查询被错误地标记为 offline。
为了规避这个问题,我们现在总是以 online: true 开始,并且只侦听 online 和 offline 事件来更新状态。
这应该会降低假阴性的可能性,但是,对于通过 serviceWorker 加载的离线应用程序(即使没有互联网连接也可以工作),这可能意味着假阳性。
在 v4 中,我们引入了将自定义 context 传递给所有 react-query 钩子的可能性。这允许在使用 MicroFrontends 时进行适当的隔离。
但是,context 是一个仅限 react 的功能。context 所做的只是让我们能够访问 queryClient。我们可以通过允许直接传入自定义 queryClient 来实现相同的隔离。 这反过来将使其他框架能够以与框架无关的方式拥有相同的功能。
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 ++]
)
在 v4 中,我们引入了使用 refetchPage 函数为无限查询定义要重新获取的页面的可能性。
但是,重新获取所有页面可能会导致 UI 不一致。此外,此选项在例如 queryClient.refetchQueries 上可用,但它仅对无限查询有效,对“普通”查询无效。
v5 包含一个新的 maxPages 选项,用于无限查询,以限制存储在查询数据中并重新获取的页面数量。此新功能处理了最初为 refetchPage 页面功能确定的用例,而没有相关问题。
您可以传递给 dehydrate 的选项已简化。查询和变更始终根据默认函数实现进行脱水。要更改此行为,请不要使用已删除的布尔选项 dehydrateMutations 和 dehydrateQueries,而是实现等效的函数 shouldDehydrateQuery 或 shouldDehydrateMutation。要获得完全不水合查询/变更的旧行为,请传入 () => false。
- dehydrateMutations?: boolean // [!code --]
- dehydrateQueries?: boolean // [!code --]
- dehydrateMutations?: boolean // [!code --]
- dehydrateQueries?: boolean // [!code --]
以前,我们将 undefined 作为 pageParam 传递给 queryFn,您可以在 queryFn 函数签名中为 pageParam 参数分配默认值。这样做的缺点是在 queryCache 中存储了 undefined,这是不可序列化的。
相反,您现在必须将显式的 initialPageParam 传递给无限查询选项。这将用作第一页的 pageParam:
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 值直接传递给 fetchNextPage 或 fetchPreviousPage 来覆盖从 getNextPageParam 或 getPreviousPageParam 返回的 pageParams。此功能根本无法与重新获取一起使用,并且未被广泛了解或使用。这也意味着 getNextPageParam 现在是无限查询所必需的。
在 v4 中,您需要显式返回 undefined 以指示没有更多可用页面。我们已将此检查范围扩大到包括 null。
在服务器上,retry 现在默认为 0 而不是 3。对于预取,我们始终默认为 0 次重试,但由于启用了 suspense 的查询现在也可以直接在服务器上执行(从 React18 开始),因此我们必须确保根本不在服务器上重试。
loading 状态已重命名为 pending,类似地,派生的 isLoading 标志已重命名为 isPending。
对于变更,status 也从 loading 更改为 pending,isLoading 标志已更改为 isPending。
最后,向查询中添加了一个新的派生 isLoading 标志,其实现为 isPending && isFetching。这意味着 isLoading 和 isInitialLoading 是相同的东西,但 isInitialLoading 现在已被弃用,并将在下一个主要版本中删除。
要了解此更改背后的原因,请查看 v5 路线图讨论。
因为它还会对变更键进行哈希处理,并且可以在 useIsMutating 和 useMutationState 的 predicate 函数内部使用,这些函数会传递变更。
React Query v5 需要 React 18.0 或更高版本。这是因为我们正在使用新的 useSyncExternalStore 钩子,该钩子仅在 React 18.0 及更高版本中可用。以前,我们一直使用 React 提供的 shim。
以前,您可以使用 contextSharing 属性在窗口中共享查询客户端上下文的第一个(至少一个)实例。这确保了如果 TanStack Query 在不同的捆绑包或微前端中使用,它们都将使用相同的上下文实例,而不管模块作用域如何。
随着 v5 中自定义上下文属性的删除,请参阅有关删除自定义上下文属性以支持自定义 queryClient 实例的部分。如果您希望在应用程序的多个包之间共享相同的查询客户端,则可以直接传递共享的自定义 queryClient 实例。
由于函数 unstable_batchedUpdates 在 React 18 中是空操作,因此它将不再自动设置为 react-query 中的批处理函数。
如果您的框架支持自定义批处理函数,您可以通过调用 notifyManager.setBatchNotifyFunction 来让 TanStack Query 知道。
例如,这是在 solid-query 中设置批处理函数的方式:
import { notifyManager } from '@tanstack/query-core'
import { batch } from 'solid-js'
notifyManager.setBatchNotifyFunction(batch)
import { notifyManager } from '@tanstack/query-core'
import { batch } from 'solid-js'
notifyManager.setBatchNotifyFunction(batch)
为了更好地支持并发功能和转换,我们对水合 API 进行了一些更改。Hydrate 组件已重命名为 HydrationBoundary,并且 useHydrate 钩子已被删除。
HydrationBoundary 不再水合变更,仅水合查询。要水合变更,请使用低级 hydrate API 或 persistQueryClient 插件。
最后,作为一个技术细节,查询水合的时间略有更改。新查询仍在渲染阶段进行水合,以便 SSR 照常工作,但缓存中已存在的任何查询现在都在效果中进行水合(只要它们的数据比缓存中的数据更新)。如果您像通常那样在应用程序启动时只进行一次水合,则不会受到影响,但是如果您正在使用服务器组件并在页面导航时向下传递新的水合数据,则可能会在页面立即重新渲染之前注意到旧数据的闪烁。
最后这个更改在技术上是一个重大更改,是为了避免在页面转换完全提交之前过早地更新_现有_页面上的内容而进行的。您无需执行任何操作。
- import { Hydrate } from '@tanstack/react-query' // [!code --]
+ import { HydrationBoundary } from '@tanstack/react-query' // [!code ++]
- <Hydrate state={dehydratedState}> // [!code --]
+ <HydrationBoundary state={dehydratedState}> // [!code ++]
<App />
- </Hydrate> // [!code --]
+ </HydrationBoundary> // [!code ++]
- import { Hydrate } from '@tanstack/react-query' // [!code --]
+ import { HydrationBoundary } from '@tanstack/react-query' // [!code ++]
- <Hydrate state={dehydratedState}> // [!code --]
+ <HydrationBoundary state={dehydratedState}> // [!code ++]
<App />
- </Hydrate> // [!code --]
+ </HydrationBoundary> // [!code ++]
queryClient.getQueryDefaults 现在将合并所有匹配的注册,而不是仅返回第一个匹配的注册。
因此,对 queryClient.setQueryDefaults 的调用现在应按特异性_递增_的顺序排列。 也就是说,应从最通用的键到最不通用的键进行注册。
例如:
+ queryClient.setQueryDefaults(['todo'], { // [!code ++]
+ retry: false, // [!code ++]
+ staleTime: 60_000, // [!code ++]
+ }) // [!code ++]
queryClient.setQueryDefaults(['todo', 'detail'], {
+ retry: true, // [!code --]
retryDelay: 1_000,
staleTime: 10_000,
})
- queryClient.setQueryDefaults(['todo'], { // [!code --]
- retry: false, // [!code --]
- staleTime: 60_000, // [!code --]
- }) // [!code --]
+ queryClient.setQueryDefaults(['todo'], { // [!code ++]
+ retry: false, // [!code ++]
+ staleTime: 60_000, // [!code ++]
+ }) // [!code ++]
queryClient.setQueryDefaults(['todo', 'detail'], {
+ retry: true, // [!code --]
retryDelay: 1_000,
staleTime: 10_000,
})
- queryClient.setQueryDefaults(['todo'], { // [!code --]
- retry: false, // [!code --]
- staleTime: 60_000, // [!code --]
- }) // [!code --]
请注意,在此特定示例中,将 retry: true 添加到 ['todo', 'detail'] 注册中,以抵消它现在从更通用的注册中继承 retry: false 的情况。维持确切行为所需的特定更改将因您的默认值而异。
v5 还带来了新功能:
我们有一种新的、简化的方式来执行乐观更新,方法是利用从 useMutation 返回的 variables:
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>
)
}
在这里,我们仅在变更运行时更改 UI 的外观,而不是直接将数据写入缓存。如果只有一个地方需要显示乐观更新,则此方法效果最佳。有关更多详细信息,请查看乐观更���文档。
无限查询在需要无限滚动或分页时非常有用。 但是,获取的页面越多,消耗的内存就越多,这也会减慢查询重新获取过程,因为所有页面都是按顺序重新获取的。
版本 5 为无限查询提供了一个新的 maxPages 选项,允许开发人员限制存储在查询数据中并随后重新获取的页面数量。 您可以根据要提供的 UX 和重新获取性能调整 maxPages 值。
请注意,无限列表必须是双向的,这要求同时定义 getNextPageParam 和 getPreviousPageParam。
无限查询可以像常规查询一样进行预取。默认情况下,仅预取查询的第一页,并将其存储在给定的 QueryKey 下。如果要预取多个页面,可以使用 pages 选项。有关更多信息,请阅读预取指南。
有关更多详细信息,请参阅 useQueries 文档。
有关更多详细信息,请参阅 experimental_createPersister 文档。
有关更多详细信息,请参阅 TypeScript 文档。
对于 v5,用于数据获取的 suspense 最终变得“稳定”。我们添加了专用的 useSuspenseQuery、useSuspenseInfiniteQuery 和 useSuspenseQueries 钩子。使用这些钩子,data 在类型级别上永远不会是潜在的 undefined:
const { data: post } = useSuspenseQuery({
// ^? const post: Post
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
})
const { data: post } = useSuspenseQuery({
// ^? const post: Post
queryKey: ['post', postId],
queryFn: () => fetchPost(postId),
})
查询钩子上的实验性 suspense: boolean 标志已被删除。
您可以在 suspense 文档中阅读更多相关信息。