Suspense
使用 Apollo Client 与 React 18 Suspense 功能
"Suspense"通常指用React 18中引入的新并发渲染引擎构建React应用的方式并发渲染引擎在React 18中引入。它也是一个特定的React API,<Suspense />
,一个允许你显示回退内容的组件,直到其子组件加载完成。
本指南探讨了Apollo Client's data fetching hooks introduced in 3.8, which leverage React's powerful Suspense features.
要跟随下面的示例,请打开我们的Suspense 演示 在 CodeSandbox 上。
使用 Suspense 获取数据
`useSuspenseQuery` 钩子触发一个网络请求,并在请求期间使调用该组件挂起。您可以将它视为 `useQuery` 的 Suspense 版本,允许您在渲染过程中获取数据时利用 React 的 Suspense 功能。
让我们看一下一个例子
import { Suspense } from 'react';import {gql,TypedDocumentNode,useSuspenseQuery} from '@apollo/client';interface Data {dog: {id: string;name: string;};}interface Variables {id: string;}interface DogProps {id: string}const GET_DOG_QUERY: TypedDocumentNode<Data, Variables> = gql`query GetDog($id: String) {dog(id: $id) {# By default, an object's cache key is a combination of# its __typename and id fields, so we should always make# sure the id is in the response so our data can be# properly cached.idnamebreed}}`;function App() {return (<Suspense fallback={<div>Loading...</div>}><Dog id="3" /></Suspense>);}function Dog({ id }: DogProps) {const { data } = useSuspenseQuery(GET_DOG_QUERY, {variables: { id },});return <>Name: {data.dog.name}</>;}
ⓘ 注意
这个示例手动为 Data
和 Variables
以及 GET_DOG_QUERY
的类型定义使用 TypedDocumentNode
。 GraphQL 代码生成器 是一个流行的工具,可以为您自动创建这些类型定义。有关将 TypeScript 代码生成器与 Apollo Client 集成的更多信息,请参阅。
在本示例中,我们的 App
组件渲染了一个 Dog
组件,该组件通过 useSuspenseQuery
获取单个狗狗的记录。当 React 首次尝试渲染 Dog
时,缓存无法满足对 GetDog
查询 的请求,因此 useSuspenseQuery
便启动了一个网络请求。Dog
组件在网络请求挂起期间暂停渲染,并触发 Suspense
边界,该边界位于 App
组件中暂停组件之上,渲染我们的 "正在加载..." 默认内容。一旦网络请求完成,Dog
将使用新缓存的 name
渲染名叫 Cookie 的斗牛犬。
你可能已经注意到,useSuspenseQuery
并不返回一个 loading
布尔值。这是因为调用 useSuspenseQuery
的组件在获取数据时总是挂起。一个推论是,当它 确实 渲染时,data
总是定义的!在 Suspense
架构中,存在 在挂起组件之外 的回退内容,可以替换之前由组件自己渲染的加载状态。
ⓘ 注意
对于 TypeScript 用户:由于 GET_DOG_QUERY
是一个 TypedDocumentNode
,我们在其中通过 Data
泛型参数指定了结果类型,所以 useSuspenseQuery
返回的 data
的 TypeScript 类型反映了这一点!这意味着当 Dog
组件渲染时,data
总是定义的,并且 data.dog
的形状为 { id: string; name: string; breed: string; }
。
更改变量
在上一个示例中,我们通过传递到一个预先编码的 id
变量来通过 useSuspenseQuery
获取单个狗狗的记录。现在,假设我们想要通过动态值获取其他狗狗的记录。我们将获取我们列表中狗狗的 name
和 id
,一旦用户选择了一个单独的狗狗,我们会获取更多的细节,包括它们的 breed
。
更新我们的示例
export const GET_DOG_QUERY: TypedDocumentNode<DogData,Variables> = gql`query GetDog($id: String) {dog(id: $id) {idnamebreed}}`;export const GET_DOGS_QUERY: TypedDocumentNode<DogsData,Variables> = gql`query GetDogs {dogs {idname}}`;function App() {const { data } = useSuspenseQuery(GET_DOGS_QUERY);const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);return (<><selectonChange={(e) => setSelectedDog(e.target.value)}>{data.dogs.map(({ id, name }) => (<option key={id} value={id}>{name}</option>))}</select><Suspense fallback={<div>Loading...</div>}><Dog id={selectedDog} /></Suspense></>);}function Dog({ id }: DogProps) {const { data } = useSuspenseQuery(GET_DOG_QUERY, {variables: { id },});return (<><div>Name: {data.dog.name}</div><div>Breed: {data.dog.breed}</div></>);}
通过 select
更改狗狗会导致组件在每次选择尚未存在于缓存中的狗狗记录时暂停。一旦我们在缓存中加载了某个狗狗的记录,再次从下拉列表中选定该狗狗不会导致组件重新暂停,因为根据我们的 cache-first
缓存优先获取策略,Apollo Client 在缓存命中后不会发起网络请求。
无挂起状态更新状态
有时我们可能想要在处理挂起的网络请求时避免显示加载中的UI,而是选择继续显示之前的渲染。要做到这一点,我们可以使用一个过渡来标记我们的更新为非紧急。这告诉React在新的数据完成加载之前保持现有UI的状态。
要将状态更新标记为过渡,我们使用React的startTransition
函数。
让我们修改我们的示例,以便在过渡期间,先前显示的狗保留在屏幕上,同时下一个狗被获取
import { useState, Suspense, startTransition } from "react";function App() {const { data } = useSuspenseQuery(GET_DOGS_QUERY);const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);return (<><selectonChange={(e) => {startTransition(() => {setSelectedDog(e.target.value);});}}>{data.dogs.map(({ id, name }) => (<option key={id} value={id}>{name}</option>))}</select><Suspense fallback={<div>Loading...</div>}><Dog id={selectedDog} /></Suspense></>);}
通过将我们的setSelectedDog
状态更新包装在React的startTransition
函数中,我们就不会再在选择新狗时看到Suspense后备!相反,之前的狗会一直保留在屏幕上,直到下一个狗的记录加载完成。
过渡期间的挂起UI显示
在先前的示例中,在选择新狗时,没有视觉效果表明正在进行获取操作。为了提供良好的视觉反馈,让我们更新我们的示例以使用React的useTransition
钩子,它为您提供一个isPending
布尔值,以确定何时发生过渡。
让我们在过渡发生时变暗选择下拉菜单
import { useState, Suspense, useTransition } from "react";function App() {const [isPending, startTransition] = useTransition();const { data } = useSuspenseQuery(GET_DOGS_QUERY);const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);return (<><selectstyle={{ opacity: isPending ? 0.5 : 1 }}onChange={(e) => {startTransition(() => {setSelectedDog(e.target.value);});}}>{data.dogs.map(({ id, name }) => (<option key={id} value={id}>{name}</option>))}</select><Suspense fallback={<div>Loading...</div>}><Dog id={selectedDog} /></Suspense></>);}
部分数据的渲染
当缓存中包含部分数据时,您可能希望立即渲染这些数据而不是挂起。要做到这一点,请使用returnPartialData
选项。
ⓘ 注意
此选项仅当与cache-first
(默认)或cache-and-network
获取策略结合使用时才有效。cache-only
目前不支持useSuspenseQuery
。有关这些获取策略的详细信息,请参阅设置获取策略。
让我们更新我们的示例以使用部分缓存数据和立即渲染
interface PartialData {dog: {id: string;name: string;};}const PARTIAL_GET_DOG_QUERY: TypedDocumentNode<PartialData,Variables> = gql`query GetDog($id: String) {dog(id: $id) {idname}}`;// Write partial data for Buck to the cache// so it is available when Dog rendersclient.writeQuery({query: PARTIAL_GET_DOG_QUERY,variables: { id: "1" },data: { dog: { id: "1", name: "Buck" } },});function App() {const client = useApolloClient();return (<Suspense fallback={<div>Loading...</div>}><Dog id="1" /></Suspense>);}function Dog({ id }: DogProps) {const { data } = useSuspenseQuery(GET_DOG_QUERY, {variables: { id },returnPartialData: true,});return (<><div>Name: {data?.dog?.name}</div><div>Breed: {data?.dog?.breed}</div></>);}
在本示例中,我们将部分数据写入Buck的缓存,以显示当查询无法完全从缓存中得到满足时的行为。useSuspenseQuery
,我们通过将returnPartialData
选项设置为true
告诉useSuspenseQuery
我们不介意渲染部分数据。Apollo Client则在后台从网络中获取缺少的查询数据。
在第一次渲染时,Buck 的名字显示在 Name
标签后面,接着是带有空值的 Breed
标签。一旦缺失的 字段 加载完成,useSuspenseQuery
将触发重新渲染并显示 Buck 的品种。
ⓘ 注意
针对 TypeScript 用户:将 returnPartialData
设置为 true
时,data
属性的返回类型会将查询类型中的所有 fields 标记为可选。当返回部分数据时,Apollo Client 无法准确地确定缓存中存在的字段。
错误处理
默认情况下,useSuspenseQuery
会抛出网络错误和 GraphQL 错误。这些错误会被最近的 错误边界 捕获并显示。
ⓘ 注意
错误边界是一个实现了 static getDerivedStateFromError
的 类组件。有关更多信息,请参阅 React 文档中关于 使用错误边界捕获渲染错误 的部分。
让我们创建一个基本的错误边界,当查询抛出错误时,它将渲染一个错误 UI:
class ErrorBoundary extends React.Component {constructor(props) {super(props);this.state = { hasError: false };}static getDerivedStateFromError(error) {return { hasError: true };}render() {if (this.state.hasError) {return this.props.fallback;}return this.props.children;}}
ⓘ 注意
在实际应用中,您的错误边界可能需要更健壮的实现。考虑在使用高度灵活和可重用的库时使用如 react-error-boundary
时。
当 GET_DOG_QUERY
在 Dog
组件中返回 GraphQL 错误或网络错误时,useSuspenseQuery
会抛出错误,最近的错误边界会渲染其备选组件。
我们的示例还没有错误边界,让我们添加一个!
function App() {const { data } = useSuspenseQuery(GET_DOGS_QUERY);const [selectedDog, setSelectedDog] = useState(data.dogs[0].id);return (<><selectonChange={(e) => setSelectedDog(e.target.value)}>{data.dogs.map(({ id, name }) => (<option key={id} value={id}>{name}</option>))}</select><ErrorBoundaryfallback={<div>Something went wrong</div>}><Suspense fallback={<div>Loading...</div>}><Dog id={selectedDog} /></Suspense></ErrorBoundary></>);}
在这里,我们使用了我们的 ErrorBoundary
组件并将其放置在我们的 Dog
组件外部。现在,当 Dog
组件中的 useSuspenseQuery
钩子抛出错误时,ErrorBoundary
会捕获错误并显示我们提供的 fallback
元素。
ⓘ 注意
在许多 React 框架中,当出现错误并且带有错误边界时,您可能在开发模式下看到错误对话框蒙版。这旨在防止开发者错过错误。
在错误旁边渲染部分数据
在某些情况下,您可能希望在错误旁边渲染部分数据。为此,将 errorPolicy
选项设置为 all
。通过设置此选项,useSuspenseQuery
避免抛出错误,而是设置钩子返回的 error
属性。要完全忽略错误,将 errorPolicy
设置为 ignore
。有关更多信息,请参阅 errorPolicy
文档。
避免请求瀑布效应
由于 useSuspenseQuery
在加载数据时挂起,所有使用 useSuspenseQuery
的组件的树可以引起 "瀑布",每个对 useSuspenseQuery
的调用都依赖于上一个调用完成,然后才能开始加载数据。这可以通过使用 useBackgroundQuery
和使用 useReadQuery
读取数据来避免。
useBackgroundQuery
在父组件中启动数据请求并返回一个 queryRef
,该引用被传递给 useReadQuery
以在子组件中读取数据。当子组件在数据加载完成之前渲染时,子组件将挂起。
让我们更新我们的示例以利用 useBackgroundQuery
:
import {useBackgroundQuery,useReadQuery,useSuspenseQuery,} from '@apollo/client';function App() {// We can start the request here, even though `Dog`// suspends and the data is read by a grandchild component.const [queryRef] = useBackgroundQuery(GET_BREEDS_QUERY);return (<Suspense fallback={<div>Loading...</div>}><Dog id="3" queryRef={queryRef} /></Suspense>);}function Dog({ id, queryRef }: DogProps) {const { data } = useSuspenseQuery(GET_DOG_QUERY, {variables: { id },});return (<>Name: {data.dog.name}<Suspense fallback={<div>Loading breeds...</div>}><Breeds queryRef={queryRef} /></Suspense></>);}interface BreedsProps {queryRef: QueryRef<BreedData>;}function Breeds({ queryRef }: BreedsProps) {const { data } = useReadQuery(queryRef);return data.breeds.map(({ characteristics }) =>characteristics.map((characteristic) => (<div key={characteristic}>{characteristic}</div>)));}
我们开始在 App
组件渲染时获取我们的 GET_BREEDS_QUERY
。网络请求在 React 渲染我们的其他组件树时在后台进行。当 Dog
组件渲染时,它获取我们的 GET_DOG_QUERY
并挂起。
当网络请求GET_DOG_QUERY
完成时,Dog
组件恢复挂起并继续渲染,达到Breeds
组件。由于我们的GET_BREEDS_QUERY
请求是使用useBackgroundQuery
在组件树上更高处启动的,因此GET_BREEDS_QUERY
的网络请求已完成!当Breeds
组件从useBackgroundQuery
提供的queryRef
读取数据时,它避免了挂起并立即使用获取的数据进行渲染。
ⓘ 注意
当GET_BREEDS_QUERY
的获取时间比GET_DOG_QUERY
更长时,useReadQuery
挂起,并且在Dog
组件中渲染了回退。
关于性能的说明
父组件中使用的useBackgroundQuery
钩子负责启动获取,但不处理读取或渲染数据。这被委托给子组件中使用的useReadQuery
钩子。这种关注点的分离提供了一种良好的性能优势,因为缓存更新可以由useReadQuery
观察到,并且只重新渲染子组件。您可能会发现这是一个有用的工具,可以优化组件结构,以避免在缓存数据更改时无必要地重新渲染父组件。
响应用户交互进行获取自3.9.0
使用钩子的useSuspenseQuery
和useBackgroundQuery
允许我们在组件挂载时立即加载数据。但是,用户交互时如何加载查询呢?例如,我们可能希望在用户将鼠标悬停在链接上时开始加载数据。
自Apollo Client3.9.0
起提供,useLoadableQuery
钩子可以在用户交互时启动网络请求。
useLoadableQuery
返回一个执行函数和一个queryRef
。执行函数在用提供的变量调用时启动网络请求。与useBackgroundQuery
类似,将queryRef
传递给子组件中的useReadQuery
会在查询加载完成之前挂起子组件。
ⓘ 注意
在执行函数首次被调用之前,queryRef
是null
。因此,任何尝试使用queryRef
读取数据的孩子组件应该进行有条件的渲染。
让我们更新我们的示例,以在从下拉列表中选择时开始加载狗的细节。
import {// ...useLoadableQuery} from '@apollo/client';function App() {const { data } = useSuspenseQuery(GET_DOGS_QUERY);const [loadDog, queryRef] = useLoadableQuery(GET_DOG_QUERY);return (<><selectonChange={(e) => loadDog({ id: e.target.value })}>{data.dogs.map(({ id, name }) => (<option key={id} value={id}>{name}</option>))}</select><Suspense fallback={<div>Loading...</div>}>{queryRef && <Dog queryRef={queryRef} />}</Suspense></>);}function Dog({ queryRef }: DogProps) {const { data } = useReadQuery(queryRef)return (<><div>Name: {data.dog.name}</div><div>Breed: {data.dog.breed}</div></>);}
通过在 onChange
处理函数中调用 loadDog
函数,我们开始检索我们的 GET_DOG_QUERY
。一旦网络请求开始,queryRef
就不再为 null
,这将渲染 Dog
组件。
useReadQuery
在网络请求完成时挂起 Dog
组件,然后返回数据到组件。因此,我们也消除了在组件状态中跟踪所选狗狗 id
的需要。
在 React 之外启动查询自3.9.0
从 Apollo Client 的 3.9.0
版本开始,查询可以在 React 之外启动。这允许你的应用程序在 React 渲染你的组件之前开始加载数据,并提供性能优势。
要预加载数据查询,你首先需要使用 createQueryPreloader
创建一个预加载数据函数。函数 createQueryPreloader
接收一个 ApolloClient
实例作为参数,并返回一个函数,当调用该函数时,将启动一个网络请求。
💡 提示
考虑将你的预加载数据函数和你的 ApolloClient
实例一起导出。这样,你可以直接导入该函数,而不需要在每次预加载数据时传递你的 ApolloClient
实例。
预加载数据函数返回一个 queryRef
,该 queryRef
传递到 useReadQuery
以读取查询数据并在加载时挂起组件。 useReadQuery
确保你的组件在预加载数据的缓存更新时保持更新。
让我们更新我们的例子,在应用绘制之前开始加载 GET_DOGS_QUERY
。
import {// ...createQueryPreloader} from '@apollo/client';// This `preloadQuery` function does not have to be created each time you// need to preload a new query. You may prefer to export this function// alongside your client instead.const preloadQuery = createQueryPreloader(client);const preloadedQueryRef = preloadQuery(GET_DOGS_QUERY);function App() {const { data } = useReadQuery(preloadedQueryRef);const [queryRef, loadDog] = useLoadableQuery(GET_DOG_QUERY)return (<><selectonChange={(e) => loadDog({ id: e.target.value })}>{data.dogs.map(({ id, name }) => (<option key={id} value={id}>{name}</option>))}</select><Suspense fallback={<div>Loading...</div>}><Dog queryRef={queryRef} /></Suspense></>);}const root = createRoot(document.getElementById('app'));root.render(<ApolloProvider client={client}><Suspense fallback={<div>Loading...</div>}><App /></Suspense></ApolloProvider>);
我们开始加载数据,而不是等 React 渲染我们的 <App />
组件。因为 preloadedQueryRef
传递到我们在 App
组件中的 useReadQuery
,因此我们仍然享有挂起功能和相关缓存更新的好处。
ⓘ 注意
卸载包含预加载数据查询的组件是安全且释放 queryRef
的。当组件重新挂起时,useReadQuery
会自动重新订阅到 queryRef
,并在任何时候发生的任何缓存更新都将立即读取,就像预先加载数据从未卸载过一样。
与数据加载路由结合使用
流行的路由器,如React Router和TanStack Router,提供了在路径组件渲染前加载数据的API。一个例子是来自ReactRouter的loader函数
在渲染路径组件之前加载数据,对于嵌套路由特别有用,可以实现数据加载的并行化。这可以防止父路径组件挂起并创建子路径组件的请求级联。
preloadQuery
与这些routerAPI配合良好,因为它让您能够利用这些优化而不牺牲在路径组件中重新渲染缓存更新的能力。
让我们用React Router的loader函数更新我们的示例,在进入我们的路径时开始加载数据。
import { useLoaderData } from 'react-router-dom';export function loader() {return preloadQuery(GET_DOGS_QUERY);}export function RouteComponent() {const queryRef = useLoaderData();const { data } = useReadQuery(queryRef);return (// ...);}
该loader函数在React Router版本6.4及以上版本中可用。
React Router调用loader函数,我们通过调用preloadQuery函数来开始加载GET_DOG_QUERY查询。由preloadQuery创建的queryRef从loader函数返回,这使得在路径组件中可访问。
当路径组件渲染时,我们从useLoaderData钩子中访问queryRef,然后将其传递给useReadQuery。我们获得了在路由生命周期早期加载数据的好处,并且路径组件维持了使用缓存更新重新渲染的能力。
ⓘ 注意
该preloadQuery函数仅与客户端路由一起使用。preloadQuery返回的queryRef无法在网络中进行序列化,因此无法与在服务器上获取数据的routers一起使用,例如Remix
在查询加载完成之前防止路径转换
默认情况下,preloadQuery
的工作方式类似于一个延迟加载器: 路由立即过渡,并且试图通过useReadQuery
读取数据的即将到来的页面会暂停,直到网络请求完成。
但如果我们想防止路由在数据完全加载前过渡怎么办?queryRef
's toPromise
方法提供了一个在网络请求完成时解决的承诺。这个承诺以queryRef
自身解决,使得它易于与useLoaderData
等钩子一起使用。
以下是一个示例
export async function loader() {const queryRef = await preloadQuery(GET_DOGS_QUERY).toPromise();return queryRef;}// You may also return the promise directly from loader.// This is equivalent to the above.export async function loader() {return preloadQuery(GET_DOGS_QUERY).toPromise();}export function RouteComponent() {const queryRef = useLoaderData();const { data } = useReadQuery(queryRef);// ...}
这指示React Router在查询加载完成后才开始路由过渡。在承诺解决后进行路由过渡时,数据会立即渲染,无需在路由组件中显示加载回退。
为什么在toPromise
中阻止访问data
?
您可能想知道为什么我们不使用查询从查询加载数据来解析toPromise
,而是解析queryRef
自身。我们希望大家利用useReadQuery
以避免错过查询的缓存更新。如果data
可用,那么在loader
函数中消费它并暴露给路由组件会很诱惑。这样做意味着您将错过缓存更新。
如果您需要在loader
函数中访问原始查询数据,直接使用client.query()
。
重新查询和分页
Apollo的 suspense 数据获取钩子返回用于通过refetch
函数重新查询query
数据和通过fetchMore
函数获取数据额外页面的函数。
让我们更新我们的示例,增加重新查询品种的能力。我们从useBackgroundQuery
返回的元组的第二个项目解构出refetch
函数。我们将确保使用React的useTransition
钩子显示一个挂起状态,让用户知道数据正在重新查询:
import { Suspense, useTransition } from "react";import {useSuspenseQuery,useBackgroundQuery,useReadQuery,gql,TypedDocumentNode,QueryRef,} from "@apollo/client";function App() {const [isPending, startTransition] = useTransition();const [queryRef, { refetch }] = useBackgroundQuery(GET_BREEDS_QUERY);function handleRefetch() {startTransition(() => {refetch();});};return (<Suspense fallback={<div>Loading...</div>}><Dogid="3"queryRef={queryRef}isPending={isPending}onRefetch={handleRefetch}/></Suspense>);}function Dog({id,queryRef,isPending,onRefetch,}: DogProps) {const { data } = useSuspenseQuery(GET_DOG_QUERY, {variables: { id },});return (<>Name: {data.dog.name}<Suspense fallback={<div>Loading breeds...</div>}><Breeds isPending={isPending} queryRef={queryRef} /></Suspense><button onClick={onRefetch}>Refetch!</button></>);}function Breeds({ queryRef, isPending }: BreedsProps) {const { data } = useReadQuery(queryRef);return data.breeds.map(({ characteristics }) =>characteristics.map((characteristic) => (<divstyle={{ opacity: isPending ? 0.5 : 1 }}key={characteristic}>{characteristic}</div>)));}
// ...import {// ...useQueryRefHandlers,} from "@apollo/client";const queryRef = preloadQuery(GET_BREEDS_QUERY);function App() {const [isPending, startTransition] = useTransition();const { refetch } = useQueryRefHandlers(queryRef);function handleRefetch() {startTransition(() => {refetch();});};return (<Suspense fallback={<div>Loading...</div>}><Dogid="3"isPending={isPending}onRefetch={handleRefetch}/></Suspense>);}// ...
function App() {const [queryRef] = useBackgroundQuery(GET_BREEDS_QUERY);return (<Suspense fallback={<div>Loading...</div>}><Dog id="3" queryRef={queryRef} /></Suspense>);}function Dog({ id, queryRef }: DogProps) {const { data } = useSuspenseQuery(GET_DOG_QUERY, {variables: { id },});const [isPending, startTransition] = useTransition();const { refetch } = useQueryRefHandlers(queryRef);function handleRefetch() {startTransition(() => {refetch();});};return (<>Name: {data.dog.name}<Suspense fallback={<div>Loading breeds...</div>}><Breeds queryRef={queryRef} isPending={isPending} /></Suspense><button onClick={handleRefetch}>Refetch!</button></>);}// ...
ⓘ 注意
使用从 useQueryRefHandlers
返回的处理程序不会阻止您使用 query 代理钩子生成处理程序
使用 queryKey
区分查询
Apollo 客户端 使用 query
和 variables
的组合来唯一标识在使用 Apollo 的 Suspense 数据获取钩子时每个查询。
如果您的应用程序渲染多个使用相同 query
和 variables
的组件,这可能会引起问题:多个钩子执行的查询共享相同的身份,导致它们 同时挂起,无论哪个组件启动或重新启动网络请求。
您可以使用 queryKey
选项来确保每个钩子都有一个唯一的身份。当提供 queryKey
时,Apollo 客户端将其用作钩子身份的一部分,加上其 query
和 variables
。
更多信息,请参阅 useSuspenseQuery
或 useBackgroundQuery
API 文档。
跳过悬停钩子
虽然 useSuspenseQuery
和 useBackgroundQuery
都有 skip
选项,但该选项仅用于易于将 useQuery
迁移,尽可能少地修改代码。它不应长期使用。
相反,您应使用 skipToken
:
import { skipToken, useSuspenseQuery } from '@apollo/client';const { data } = useSuspenseQuery(query,id ? { variables: { id } } : skipToken);
import { skipToken, useBackgroundQuery } from '@apollo/client';const [queryRef] = useBackgroundQuery(query,id ? { variables: { id } } : skipToken);
React 服务器组件 (RSC)
与 Next.js 13 应用路由一起使用
在 Next.js v13 中,Next.js 的新 App Router 为 React 社区带来了首个全面支持 React Server Components (RSC) 以及流式 SSR 的框架,将 Suspense 作为应用路由层到最低层的首类概念集成。
为了与这些功能集成,我们 Apollo Client 团队发布了一个实验性包,@apollo/experimental-nextjs-app-support
,它允许无缝使用 Apollo Client 和 RSC 以及流式 SSR,是数据获取库中最早实现这一目标的之一。请参阅其 README 和我们的 入门博客文章 了解更多详情。
在流式 SSR 过程中使用 useBackgroundQuery
进行获取数据时
在客户端渲染的应用程序中,useBackgroundQuery
可以用来避免请求瀑布,但在使用 App Router 的应用程序中其影响可能更加明显。这是因为服务器可以开始将内容流式传输到客户端,从而带来更大的性能提升。
错误处理
在纯客户端渲染的应用中,组件抛出的错误总是由最近的 错误边界 捕获并显示。
在服务器使用以下其中一个流式服务器渲染API时抛出的错误会被以不同的方式处理。更多详细信息,请参考React文档。
后续阅读
想查看一个使用Apollo Client的Suspense钩子(以及Apollo Client 3.8引入的其他许多新功能)的大型代码库,请查看GitHub上的Spotify Showcase。这是一个全栈Web应用程序,通过使用Apollo Client、Apollo Server和GraphOS来构建Spotify标志性UI的克隆。
useSuspenseQuery API
有关useSuspenseQuery
的更多详细信息,请参阅其API文档。
useBackgroundQuery API
有关useBackgroundQuery
的更多详细信息,请参阅其API文档。
useLoadableQuery API
有关useLoadableQuery
的更多详细信息,请参阅其API文档。
useQueryRefHandlers API
有关useQueryRefHandlers
的更多详细信息,请参阅其API文档。
useReadQuery API
有关useReadQuery
的更多详细信息,请参阅其API文档。