加入我们,一起于10月8日至10日在纽约市参加GraphQL联邦和API平台工程的最新的技巧、趋势和新闻分享。加入我们,参加2024年纽约市的GraphQL峰会
文档
免费开始
3.8.0

Suspense

使用 Apollo Client 与 React 18 Suspense 功能


"Suspense"通常指用React 18中引入的新并发渲染引擎构建React应用的方式并发渲染引擎在React 18中引入。它也是一个特定的React API,<Suspense />,一个允许你显示回退内容的组件,直到其子组件加载完成。

本指南探讨了'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.
id
name
breed
}
}
`;
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}</>;
}

注意

这个示例手动为 DataVariables 以及 GET_DOG_QUERY 的类型定义使用 TypedDocumentNodeGraphQL 代码生成器 是一个流行的工具,可以为您自动创建这些类型定义。有关将 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 获取单个狗狗的记录。现在,假设我们想要通过动态值获取其他狗狗的记录。我们将获取我们列表中狗狗的 nameid,一旦用户选择了一个单独的狗狗,我们会获取更多的细节,包括它们的 breed

更新我们的示例

export const GET_DOG_QUERY: TypedDocumentNode<
DogData,
Variables
> = gql`
query GetDog($id: String) {
dog(id: $id) {
id
name
breed
}
}
`;
export const GET_DOGS_QUERY: TypedDocumentNode<
DogsData,
Variables
> = gql`
query GetDogs {
dogs {
id
name
}
}
`;
function App() {
const { data } = useSuspenseQuery(GET_DOGS_QUERY);
const [selectedDog, setSelectedDog] = useState(
data.dogs[0].id
);
return (
<>
<select
onChange={(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 (
<>
<select
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>
</>
);
}

通过将我们的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 (
<>
<select
style={{ 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) {
id
name
}
}
`;
// Write partial data for Buck to the cache
// so it is available when Dog renders
client.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_QUERYDog 组件中返回 GraphQL 错误或网络错误时,useSuspenseQuery 会抛出错误,最近的错误边界会渲染其备选组件。

我们的示例还没有错误边界,让我们添加一个!

function App() {
const { data } = useSuspenseQuery(GET_DOGS_QUERY);
const [selectedDog, setSelectedDog] = useState(
data.dogs[0].id
);
return (
<>
<select
onChange={(e) => setSelectedDog(e.target.value)}
>
{data.dogs.map(({ id, name }) => (
<option key={id} value={id}>
{name}
</option>
))}
</select>
<ErrorBoundary
fallback={<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

使用钩子的useSuspenseQueryuseBackgroundQuery允许我们在组件挂载时立即加载数据。但是,用户交互时如何加载查询呢?例如,我们可能希望在用户将鼠标悬停在链接上时开始加载数据。

Apollo Client3.9.0起提供,useLoadableQuery钩子可以在用户交互时启动网络请求。

useLoadableQuery返回一个执行函数和一个queryRef。执行函数在用提供的调用时启动网络请求。与useBackgroundQuery类似,将queryRef传递给子组件中的useReadQuery会在查询加载完成之前挂起子组件。

注意

在执行函数首次被调用之前,queryRefnull。因此,任何尝试使用queryRef读取数据的孩子组件应该进行有条件的渲染。

让我们更新我们的示例,以在从下拉列表中选择时开始加载狗的细节。

import {
// ...
useLoadableQuery
} from '@apollo/client';
function App() {
const { data } = useSuspenseQuery(GET_DOGS_QUERY);
const [loadDog, queryRef] = useLoadableQuery(GET_DOG_QUERY);
return (
<>
<select
onChange={(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 的需要。

从 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 (
<>
<select
onChange={(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 RouterTanStack Router,提供了在路径组件渲染前加载数据的API。一个例子是来自Reactloader函数

在渲染路径组件之前加载数据,对于嵌套路由特别有用,可以实现数据加载的并行化。这可以防止父路径组件挂起并创建子路径组件的请求级联。

preloadQuery与这些API配合良好,因为它让您能够利用这些优化而不牺牲在路径组件中重新渲染缓存更新的能力。

让我们用React Routerloader函数更新我们的示例,在进入我们的路径时开始加载数据。

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创建的queryRefloader函数返回,这使得在路径组件中可访问。

当路径组件渲染时,我们从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>}>
<Dog
id="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) => (
<div
style={{ 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>}>
<Dog
id="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 客户端 使用 queryvariables 的组合来唯一标识在使用 Apollo 的 Suspense 数据获取钩子时每个查询。

如果您的应用程序渲染多个使用相同 queryvariables 的组件,这可能会引起问题:多个钩子执行的查询共享相同的身份,导致它们 同时挂起,无论哪个组件启动或重新启动网络请求。

您可以使用 queryKey 选项来确保每个钩子都有一个唯一的身份。当提供 queryKey 时,Apollo 客户端将其用作钩子身份的一部分,加上其 queryvariables

更多信息,请参阅 useSuspenseQueryuseBackgroundQuery API 文档。

跳过悬停钩子

虽然 useSuspenseQueryuseBackgroundQuery 都有 skip 选项,但该选项仅用于易于将 useQuery 迁移,尽可能少地修改代码。它不应长期使用。

相反,您应使用 skipToken:

与 useSuspenseQuery 一起推荐的 skipToken 的使用方法
import { skipToken, useSuspenseQuery } from '@apollo/client';
const { data } = useSuspenseQuery(
query,
id ? { variables: { id } } : skipToken
);
与 useBackgroundQuery 一起推荐的 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来构建Spotify标志性UI的克隆。

useSuspenseQuery API

有关useSuspenseQuery的更多详细信息,请参阅其API文档

useBackgroundQuery API

有关useBackgroundQuery的更多详细信息,请参阅其API文档

useLoadableQuery API

有关useLoadableQuery的更多详细信息,请参阅其API文档

useQueryRefHandlers API

有关useQueryRefHandlers的更多详细信息,请参阅其API文档

useReadQuery API

有关useReadQuery的更多详细信息,请参阅其API文档

上一页
查询
下一页
突变
评价文章评价在GitHub上编辑编辑论坛Discord

©2024Apollo Graph Inc.,贸易名称 Apollo GraphQL。

隐私政策

公司