Suspense
React Query 也可与 React 的 Suspense 数据拉取(Data Fetching)API 配合使用。为此,我们提供了专用的 Hook:
启用 suspense 模式后,status 状态和 error 对象将不再需要,取而代之的是使用 React.Suspense 组件(包括使用 fallback 属性,以及借助 React 错误边界捕获错误)。请阅读 重置错误边界,并查看 Suspense 示例,了解如何配置 suspense 模式。
//: # 'Info'
若希望修改(mutation)也像查询(query)一样将错误传播至最近的错误边界,你也可以将 throwOnError 选项设为 true。
为某个查询启用 suspense 模式:
1import { useSuspenseQuery } from '@tanstack/react-query'2 3const { data } = useSuspenseQuery({ queryKey, queryFn })该方式在 TypeScript 中表现良好,因为 data 被保证已定义(错误与加载状态均由 Suspense 和错误边界处理)。
但反过来说,你因此无法条件性地启用或禁用该查询。对于依赖型查询(dependent Queries),这通常并非必需——因为在 suspense 模式下,组件内所有查询会以串行方式拉取。
此查询也不存在 placeholderData。为防止更新期间 UI 被 fallback 替换,请将更改 QueryKey 的更新操作包裹在 startTransition 中。
//: # 'PlaceholderData'
throwOnError 默认行为
并非所有错误都会默认抛出至最近的错误边界——我们仅在无其他可展示数据时才抛出错误。这意味着:只要查询曾在缓存中成功获取过数据,组件就会正常渲染,即使当前数据已 stale。因此 throwOnError 的默认值为:
1throwOnError: (error, query) => typeof query.state.data === 'undefined'由于你无法更改 throwOnError(否则可能导致 data 变为潜在的 undefined),若希望所有错误均由错误边界统一处理,则需手动抛出错误:
1import { useSuspenseQuery } from '@tanstack/react-query'2 3const { data, error, isFetching } = useSuspenseQuery({ queryKey, queryFn })4 5if (error && !isFetching) {6 throw error7}8 9// continue rendering data重置错误边界
无论你在查询中使用 suspense 还是 throwOnError,当发生错误后重新渲染时,都需要一种机制告知查询:你希望重试。
查询错误可通过 QueryErrorResetBoundary 组件或 useQueryErrorResetBoundary Hook 重置。
使用组件时,它将在该组件作用域内重置所有查询错误:
1import { QueryErrorResetBoundary } from '@tanstack/react-query'2import { ErrorBoundary } from 'react-error-boundary'3 4const App = () => (5 <QueryErrorResetBoundary>6 {({ reset }) => (7 <ErrorBoundary8 onReset={reset}9 fallbackRender={({ resetErrorBoundary }) => (10 <div>11 There was an error!12 <button onClick={() => resetErrorBoundary()}>Try again</button>13 </div>14 )}15 >16 <Page />17 </ErrorBoundary>18 )}19 </QueryErrorResetBoundary>20)使用 Hook 时,它将在最近的 QueryErrorResetBoundary 作用域内重置所有查询错误;若未定义任何边界,则全局重置:
1import { useQueryErrorResetBoundary } from '@tanstack/react-query'2import { ErrorBoundary } from 'react-error-boundary'3 4const App = () => {5 const { reset } = useQueryErrorResetBoundary()6 return (7 <ErrorBoundary8 onReset={reset}9 fallbackRender={({ resetErrorBoundary }) => (10 <div>11 There was an error!12 <button onClick={() => resetErrorBoundary()}>Try again</button>13 </div>14 )}15 >16 <Page />17 </ErrorBoundary>18 )19}渲染时拉取 vs 边渲染边拉取
开箱即用时,React Query 在 suspense 模式下天然适配 渲染时拉取(Fetch-on-render) 场景,无需额外配置。这意味着:当组件尝试挂载时,它们会触发查询拉取并进入 suspend 状态,但仅发生在你导入并挂载这些组件之后。若想进阶实现 边渲染边拉取(Render-as-you-fetch) 模型,我们建议在路由回调和/或用户交互事件中实现 预取(Prefetching),以便在组件挂载前、甚至在其父组件导入或挂载前就启动查询加载。
服务端 Suspense 与流式传输(streaming)
若你正在使用 NextJs,可尝试我们的 实验性 服务端 Suspense 集成:@tanstack/react-query-next-experimental。该包允许你在客户端组件中直接调用 useSuspenseQuery 实现服务端数据拉取。随后,结果将随 SuspenseBoundary 的解析过程从服务端流式传输至客户端。
要实现此功能,请将你的应用包裹在 ReactQueryStreamedHydration 组件中:
1// app/providers.tsx2'use client'3 4import {5 environmentManager,6 QueryClient,7 QueryClientProvider,8} from '@tanstack/react-query'9import * as React from 'react'10import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'11 12function makeQueryClient() {13 return new QueryClient({14 defaultOptions: {15 queries: {16 // With SSR, we usually want to set some default staleTime17 // above 0 to avoid refetching immediately on the client18 staleTime: 60 * 1000,19 },20 },21 })22}23 24let browserQueryClient: QueryClient | undefined = undefined25 26function getQueryClient() {27 if (environmentManager.isServer()) {28 // Server: always make a new query client29 return makeQueryClient()30 } else {31 // Browser: make a new query client if we don't already have one32 // This is very important, so we don't re-make a new client if React33 // suspends during the initial render. This may not be needed if we34 // have a suspense boundary BELOW the creation of the query client35 if (!browserQueryClient) browserQueryClient = makeQueryClient()36 return browserQueryClient37 }38}39 40export function Providers(props: { children: React.ReactNode }) {41 // NOTE: Avoid useState when initializing the query client if you don't42 // have a suspense boundary between this and the code that may43 // suspend because React will throw away the client on the initial44 // render if it suspends and there is no boundary45 const queryClient = getQueryClient()46 47 return (48 <QueryClientProvider client={queryClient}>49 <ReactQueryStreamedHydration>50 {props.children}51 </ReactQueryStreamedHydration>52 </QueryClientProvider>53 )54}欲了解更多信息,请参阅 Next.js Suspense 流式传输示例 和 高级渲染与注水(Hydration)指南。 //: # 'Streaming'