Suspense

sup

Suspense(挂起)

React Query 也可以与 React 的 Suspense for Data Fetching API 一起使用。对此,React Query 提供了一些专门的 Hook:

  • useSuspenseQuery

  • useSuspenseInfiniteQuery

  • useSuspenseQueries

此外,你还可以使用 useQuery().promiseReact.use()(实验性功能)。

Suspense 模式下,状态(status)和错误对象(error)不再需要,它们由 React.Suspense 组件 处理(包括使用 fallback 属性提供回退 UI,并利用 React 错误边界(Error Boundaries) 捕获错误)。 请阅读 Resetting Error Boundaries 以及 Suspense 示例 了解如何正确设置 Suspense 模式。

如果你希望 Mutation(变更) 也像 Query 一样将错误抛给最近的 Error Boundary,你可以设置 throwOnError: true


启用 Suspense 模式

import { useSuspenseQuery } from '@tanstack/react-query'

const { data } = useSuspenseQuery({ queryKey, queryFn })

在 TypeScript 中,这种方式很好用,因为 data 保证是已定义的(因为错误和加载状态已经由 Suspense 和 Error Boundary 处理)。

注意:

  • 无法 动态开启/关闭该 Query(enabled 选项不可用)。

  • placeholderData 在 Suspense 模式下不可用

  • 避免 UI 在更新时被 fallback 取代,你可以使用 startTransition 包裹 QueryKey 变化的更新。


throwOnError 的默认行为

不是所有错误 都会默认抛给最近的 Error Boundary,只有当 Query 没有可用数据 时,错误才会被抛出:

throwOnError: (error, query) => typeof query.state.data === 'undefined'

由于 throwOnError 不能被修改(否则 data 可能变为 undefined),如果你希望所有错误都被 Error Boundary 处理,你需要手动抛出错误:

import { useSuspenseQuery } from '@tanstack/react-query'

const { data, error, isFetching } = useSuspenseQuery({ queryKey, queryFn })

if (error && !isFetching) {
  throw error
}

// 继续渲染 data

重置错误边界(Resetting Error Boundaries)

无论你是 使用 Suspense 还是 设置 throwOnError,当错误发生后,你需要一种方法来 重置错误状态 并重新尝试请求数据。 React Query 提供了两种方式来 重置 Query 的错误状态

1. QueryErrorResetBoundary 组件

该组件会 重置其内部所有 Query 的错误状态

import { QueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'

const App = () => (
  <QueryErrorResetBoundary>
    {({ reset }) => (
      <ErrorBoundary
        onReset={reset}
        fallbackRender={({ resetErrorBoundary }) => (
          <div>
            There was an error!
            <Button onClick={() => resetErrorBoundary()}>Try again</Button>
          </div>
        )}
      >
        <Page />
      </ErrorBoundary>
    )}
  </QueryErrorResetBoundary>
)

2. useQueryErrorResetBoundary Hook

这个 Hook 会 重置最近的 QueryErrorResetBoundary 内部的错误状态,如果没有 QueryErrorResetBoundary,则会 全局重置错误状态

import { useQueryErrorResetBoundary } from '@tanstack/react-query'
import { ErrorBoundary } from 'react-error-boundary'

const App = () => {
  const { reset } = useQueryErrorResetBoundary()
  return (
    <ErrorBoundary
      onReset={reset}
      fallbackRender={({ resetErrorBoundary }) => (
        <div>
          There was an error!
          <Button onClick={() => resetErrorBoundary()}>Try again</Button>
        </div>
      )}
    >
      <Page />
    </ErrorBoundary>
  )
}

Fetch-on-render vs Render-as-you-fetch

React Query 在 Suspense 模式下 默认使用 Fetch-on-render,这意味着:

  • 组件 在挂载时 触发数据请求。

  • 组件会 挂起(Suspend),直到数据加载完成。

如果你希望 提前加载数据,可以使用 预取(Prefetching) 技术,在 路由回调用户交互事件预加载 Query,从而实现 Render-as-you-fetch 模型。


在服务器端使用 Suspense(Streaming)

如果你使用 Next.js,可以使用 @tanstack/react-query-next-experimental 来支持 服务端 Suspense Streaming。 这可以让你在 客户端组件 中调用 useSuspenseQuery,并在服务器端预取数据,结果会被 流式传输(Streaming) 到客户端。

如何启用 Streaming

// app/providers.tsx
'use client'

import {
  isServer,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'
import * as React from 'react'
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'

function makeQueryClient() {
  return new QueryClient({
    defaultOptions: {
      queries: {
        // 在 SSR 场景下,通常会设置一个默认的 staleTime,避免客户端立即重新请求
        staleTime: 60 * 1000,
      },
    },
  })
}

let browserQueryClient: QueryClient | undefined = undefined

function getQueryClient() {
  if (isServer) {
    return makeQueryClient()
  } else {
    if (!browserQueryClient) browserQueryClient = makeQueryClient()
    return browserQueryClient
  }
}

export function Providers(props: { children: React.ReactNode }) {
  const queryClient = getQueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryStreamedHydration>
        {props.children}
      </ReactQueryStreamedHydration>
    </QueryClientProvider>
  )
}

更多信息,请查看 Next.js Suspense Streaming 示例高级渲染 & Hydration 指南


使用 useQuery().promiseReact.use()(实验性)

最后更新于

这有帮助吗?