6. 定义一个查询
2m

概览

现在我们已 并处理好 types 的生成,我们可以为客户端提供要执行的第一个

在这一课中,我们将

  • 给我们的 React 组件一个 以便执行
  • 处理响应数据的加载、错误和呈现状态

📦 组件中的一个查询

我们的曲目页面的代码位于 src/pages/tracks.tsx. 目前,该页面仅显示我们之前看到的空白布局。让我们向其中添加一个 定义。

就像定义模式时一样,我们需要用 字符串中的所有 gql 函数包装。让我们导入 gql:

src/pages/tracks.tsx
import { gql } from "../__generated__/";

接下来,我们将声明一个名为 GET_TRACKS 的常量,其中包含一个空 字符串(根据惯例, 常量为 ALL_CAPS):

const GET_TRACKS = gql(`
# Query goes here
`);

注意:我们将从我们的 __generated__/index.ts 文件中 gql 导入作为函数,括号中包含反引号和 !

现在,还记得我们构建的 以在 Apollo Explorer 中检索轨道数据吗?很方便,那正是我们需要

返回到 Explorer,我们将在其中从 Sandbox 集合中访问

http://studio.apollographql.com/sandbox/explorer

Opening the Operation Collections panel to access a saved operation.

当从我们的集合中点击 TracksForHome 时,保存的 会自动插入到 Operation 面板的新选项卡中。

http://studio.apollographql.com/sandbox/explorer

Clicking on an operation saved in a collection to insert it into the Operation panel.

让我们复制,并返回到我们的代码中。

我们现在可以将直接粘贴到我们空的gql 函数中。

/** GET_TRACKS query to retrieve all tracks */
const GET_TRACKS = gql(`
query GetTracks {
tracksForHome {
id
title
thumbnail
length
modulesCount
author {
id
name
photo
}
}
}
`);

现在我们的前端代码包含了一个实际的 ,我们可以再次运行我们的npm run generate函数,让代码生成器扫描并预测我们应用将要发送的。它将使用此信息来确定我们操作的 TypeScript 类型。运行generate命令:

npm run generate

太棒了!现在我们生成的类型理解了我们会发送什么样的,以及我们期望得到什么样的数据。

我们的已准备好执行。让我们最终在我们的主页上展示一些太空猫!

📡 使用useQuery执行

若要执行查询,我们将使用useQuery挂钩。

useQuery挂钩作为 字符串作为带入。

当我们的组件渲染时,useQuery返回一个包含loadingerrordata属性的对象,我们可以使用这些属性来渲染我们的用户界面。让我们将所有这些放到代码中。

注意:查看访问官方 Apollo 文档中关于 useQuery 钩子的部分了解此功能的更多信息。

首先,我们需要从 @apollo/client 包中导入 useQuery

tracks.tsx
import { gql } from "../__generated__";
import { useQuery } from "@apollo/client";

现在,在我们的 Tracks 函数式组件中(在打开的花括号下面),我们将从 useQuery 钩子中声明三个解构常量: loadingerrordata。我们用 GET_TRACKS 作为其 调用 useQuery

const { loading, error, data } = useQuery(GET_TRACKS);

在下面,我们将首先使用 loading 常量:

if (loading) return "Loading...";

只要 loadingtrue(表明 仍在进行中),该组件只会呈现一条 Loading... 消息。

loading 为 false 时, 已完成。这意味着我们有 data,或者我们有 error

让我们添加另一个处理 error 状态的条件语句:

if (error) return `Error! ${error.message}`;

如果我们没有错误,我们一定有数据!现在,我们只需要使用 JSON.stringify 来转储我们的原始数据对象,看看会发生什么。

<Layout grid>{JSON.stringify(data)}</Layout>

添加完所有这些内容后,这就是已完成的 Tracks 组件的样子。确保你的与此匹配!

const Tracks = () => {
const { loading, error, data } = useQuery(GET_TRACKS);
if (loading) return "Loading...";
if (error) return `Error! ${error.message}`;
return <Layout grid>{JSON.stringify(data)}</Layout>;
};

我们重启 app。我们首先看到加载消息,然后是原始 JSON 响应。响应包括 tracksForHome 对象(我们的 的名称),其中包含一组 Track 对象。到目前为止,看起来还不错!现在,我们将在实际视图中使用此数据。

https://127.0.0.1:3000

The JSON response to our query, outputted directly into the UI

渲染 TrackCard

幸运的是,我们已经拥有准备就绪的 TrackCard 组件。我们需要导入组件并将响应数据馈入其中,但首先让我们打开 /src/containers/track-card.tsx 以了解它是如何工作的。

/**
* Track Card component renders basic info in a card format
* for each track populating the tracks grid homepage.
*/
const TrackCard: React.FC<{ track: any }> = ({ track }) => {
const { title, thumbnail, author, length, modulesCount, id } = track;
//...
};

我们立即可以看到 TrackCard 组件接受一个名为 track的道具,但目前其类型是 any。现在,我们已经从 生成了类型,我们可以修复此问题并更准确地描述 track 道具应提供的数据类型。

在文件的顶部,让我们导入 Track 类型,该类型存在于 __generated__ 文件夹的 graphql 文件中。

src/containers/track-card.tsx
import type { Track } from '../__generated__/graphql'

我们可以使用 Track 类型来设置 track 道具的数据类型,替换 any

/**
* Track Card component renders basic info in a card format
* for each track populating the tracks grid homepage.
*/
const TrackCard: React.FC<{ track: Track }> = ({ track }) => {
const { title, thumbnail, author, length, modulesCount, id } = track;
//...
};

现在,如果您将鼠标悬停在我们刚刚添加的 Track 类型上,您会看到我们在后端架构中定义的确切类型细分。我们确切地知道了此类型数据的可用详细信息!让我们分解它们。

组件采用 track 道具并使用其 titlethumbnailauthorlengthmodulesCountid。所以,我们只需要在 TrackCard 中传递一个 Track 对象,该对象来自我们的 响应。

让我们返回 src/pages/tracks.tsx。我们已经看到我们的 GET_TRACKS 的服务器响应包括一个 tracksForHome 键,其中包含曲目数组。

首先,让我们导入 TrackCard 组件。

pages/tracks.tsx
import TrackCard from "../containers/track-card";

要为每个曲目创建一个卡片,我们将通过 tracksForHome 数组进行映射,并使用其对应的曲目数据作为其属性返回一个 TrackCard 组件:

<Layout grid>
{data?.tracksForHome?.map((track) => (
<TrackCard key={track.id} track={track} />
))}
</Layout>

我们会立即发现 track 属性上存在错误!


为了了解上下文,我们来看看 代码生成器为我们生成的 Track 类型。

/** A track is a group of Modules that teaches about a specific topic */
export type Track = {
__typename?: 'Track';
/** The track's main Author */
author: Author;
/** The track's complete description, can be in markdown format */
description?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
/** The track's approximate length to complete, in minutes */
length?: Maybe<Scalars['Int']['output']>;
/** The track's complete array of Modules */
modules: Array<Module>;
/** The number of modules this track contains */
modulesCount?: Maybe<Scalars['Int']['output']>;
/** The number of times a track has been viewed */
numberOfViews?: Maybe<Scalars['Int']['output']>;
/** The track's illustration to display in track card or track page detail */
thumbnail?: Maybe<Scalars['String']['output']>;
/** The track's title */
title: Scalars['String']['output'];
};

在这里,我们可以看到 modules 不是 我们 Track 类型的可选属性。由于我们在这个组件中进行的 包括 modules 详细信息,因此 TypeScript 会让我们知道我们违反了此 Track 类型的规则之一。


为了解决此问题,我们将跳回 containers/track-card.tsx。在这里,我们将更新 TrackCard 的类型签名,以 省略 modules 从它在传递给它的 Track 类型上所必需的属性中。


const TrackCard: React.FC<{ track: Omit<Track, "modules"> }> = ({ track }) => {
const { title, thumbnail, author, length, modulesCount, id } = track;
// ... TrackCard body
}

这允许我们传递 track 属性一个对象,该对象 基本上 符合 Track TypeScript 类型,但不包括其 modules

我们刷新浏览器,瞧!我们得到一堆带有酷太空猫缩略图的漂亮的卡片。我们的轨道名称、时长、模块数量和作者信息都得益于我们的 TrackCard 组件而漂亮地显示出来。太棒了!

https://127.0.0.1:3000

The UI of our Catstronauts app, displaying a number of track cards

包装查询结果

刷新浏览器时,你可能注意到了,因为我们返回 loading 消息作为一个简单的字符串,我们 暂时显示组件的整个布局和导航栏,因为它正在加载中( error 消息也存在同样的问题)。我们应该确保我们的 UI 行为在 的所有阶段中保持一致。

这就是我们的 QueryResult 辅助组件派上用场的地方。这不是一个直接由 Apollo 库提供的组件。我们添加它来使用 结果在整个应用程序中以一致、可预测的方式。

我们打开 components/query-result。此组件将 useQuery 钩子的返回值作为道具。然后,它执行基本的条件逻辑来呈现一个旋转器、一条错误消息或其子组件:

components/query-result.tsx
const QueryResult: React.FC<PropsWithChildren<QueryResultProps>> = ({
loading,
error,
data,
children,
}): React.ReactElement<any, any> | null => {
if (error) {
return <p>ERROR: {error.message}</p>;
}
if (loading) {
return (
<SpinnerContainer>
<LoadingSpinner data-testid="spinner" size="large" theme="grayscale" />
</SpinnerContainer>
);
}
if (data) {
return <>{children}</>;
}
return <p>Nothing to show...</p>;
};

返回我们的 tracks.tsx 文件,我们导入 QueryResult 到顶部:

import QueryResult from "../components/query-result";

我们现在可以在该文件中删除处理 loadingerror 状态的行,因为 QueryResult 组件将改而处理它们。

const Tracks = () => {
const { loading, error, data } = useQuery(GET_TRACKS);
- if (loading) return "Loading...";
- if (error) return `Error! ${error.message}`;
return (
<Layout grid>
{data?.tracksForHome?.map((track) => (
<TrackCard key={track.id} track={track} />
))}
</Layout>
);
};

我们在 QueryResult 周围包装我们的 map 函数,并给它需要的道具:

return (
<Layout grid>
<QueryResult error={error} loading={loading} data={data}>
{data?.tracksForHome?.map((track) => (
<TrackCard key={track.id} track={track} />
))}
</QueryResult>
</Layout>
);

刷新浏览器,在加载时我们得到一个漂亮的旋转器,然后我们的卡片出现了!

https://127.0.0.1:3000

The UI of our Catstronauts app, displaying a number of track cards

在所有这些代码之后, tracks.tsx 文件应该看起来像这样:

就这样!我们的主页填充了一组酷炫的轨道卡片,就像我们在最初的模型中布局的那样。

任务!

练习

代码挑战!

创建一个 ListSpaceCats 查询,带有 spaceCats 查询字段及其 nameagemissions 选择集。对于 missions 字段,选择 namedescription

正在加载...
加载进度
以下哪些是创建客户端查询时的最佳做法?
代码挑战!

使用带有 SPACECATS 查询的 useQuery 挂钩,并从结果中解构 loadingerrordata 属性

useQuery 挂钩用于干什么?

关键要点

  • useQuery 挂钩是 React 应用程序中执行查询的主要 API。
  • useQuery 挂钩返回一个包含 loadingerrordata 属性的对象,我们可以使用这些属性来确定 UI 中的元素。

接下来

我们的主页看起来不错,但我们无处可去:下一步,让我们探讨如何设置我们的应用程序以仅显示一个 一个曲目对象。

上一步

分享一下你对此课程的问题和评论

本课程目前正在

测试版
.您的反馈有助于我们改进!如果你遇到困难或感到困惑,请告诉我们,我们会帮助你。所有评论都是公开的,并且必须遵循 阿波罗行为准则。请注意,我们可能会删除已解决或已处理的评论。

您需要一个 GitHub 帐户才能在下面发帖。还没有一个? 改为在我们的 Odyssey 论坛中发帖。