概览
现在我们已 初始化了 Apollo Client并处理好 types 的生成,我们可以为客户端提供要执行的第一个 查询。
在这一课中,我们将
- 给我们的 React 组件一个 GraphQL 查询 以便执行
- 处理响应数据的加载、错误和呈现状态
📦 组件中的一个查询
我们的曲目页面的代码位于 src/pages/tracks.tsx
. 目前,该页面仅显示我们之前看到的空白布局。让我们向其中添加一个 query 定义。
就像定义模式时一样,我们需要用 GraphQL 字符串中的所有 gql
函数包装。让我们导入 gql
:
import { gql } from "../__generated__/";
接下来,我们将声明一个名为 GET_TRACKS
的常量,其中包含一个空 GraphQL 字符串(根据惯例,query 常量为 ALL_CAPS
):
const GET_TRACKS = gql(`# Query goes here`);
注意:我们将从我们的 __generated__/index.ts
文件中 gql
导入作为函数,括号中包含反引号和 operation!
现在,还记得我们构建的 query 以在 Apollo Explorer 中检索轨道数据吗?很方便,那正是我们需要 query!
返回到 Explorer,我们将在其中从 Sandbox operation 集合中访问 query。
当从我们的集合中点击 TracksForHome
时,保存的 query 会自动插入到 Operation 面板的新选项卡中。
让我们复制查询,并返回到我们的代码中。
我们现在可以将查询直接粘贴到我们空的gql
函数中。
/** GET_TRACKS query to retrieve all tracks */const GET_TRACKS = gql(`query GetTracks {tracksForHome {idtitlethumbnaillengthmodulesCountauthor {idnamephoto}}}`);
现在我们的前端代码包含了一个实际的GraphQL 操作,我们可以再次运行我们的npm run generate
函数,让GraphQL代码生成器扫描并预测我们应用将要发送的操作。它将使用此信息来确定我们操作的 TypeScript 类型。运行generate
命令:
npm run generate
太棒了!现在我们生成的类型理解了我们会发送什么样的查询,以及我们期望得到什么样的数据。
我们的查询已准备好执行。让我们最终在我们的主页上展示一些太空猫!
📡 使用useQuery
执行
若要执行查询,我们将使用Apollo Client的useQuery
挂钩。
那useQuery
挂钩作为GraphQL 查询字符串作为参数带入。
当我们的组件渲染时,useQuery
返回一个包含loading
、error
和data
属性的对象,我们可以使用这些属性来渲染我们的用户界面。让我们将所有这些放到代码中。
注意:查看访问官方 Apollo 文档中关于 useQuery
钩子的部分了解此功能的更多信息。
首先,我们需要从 @apollo/client
包中导入 useQuery
:
import { gql } from "../__generated__";import { useQuery } from "@apollo/client";
现在,在我们的 Tracks
函数式组件中(在打开的花括号下面),我们将从 useQuery
钩子中声明三个解构常量: loading
、 error
和 data
。我们用 GET_TRACKS
查询作为其 参数调用 useQuery
:
const { loading, error, data } = useQuery(GET_TRACKS);
在下面,我们将首先使用 loading
常量:
if (loading) return "Loading...";
只要 loading
为 true
(表明 查询仍在进行中),该组件只会呈现一条 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
对象。到目前为止,看起来还不错!现在,我们将在实际视图中使用此数据。
渲染 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
。现在,我们已经从 GraphQL 服务器生成了类型,我们可以修复此问题并更准确地描述 track
道具应提供的数据类型。
在文件的顶部,让我们导入 Track
类型,该类型存在于 __generated__
文件夹的 graphql
文件中。
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
道具并使用其 title
、thumbnail
、author
、length
、modulesCount
和 id
。所以,我们只需要在 TrackCard
中传递一个 Track
对象,该对象来自我们的 查询响应。
让我们返回 src/pages/tracks.tsx
。我们已经看到我们的 GET_TRACKS
GraphQL 查询 的服务器响应包括一个 tracksForHome
键,其中包含曲目数组。
首先,让我们导入 TrackCard
组件。
import TrackCard from "../containers/track-card";
要为每个曲目创建一个卡片,我们将通过 tracksForHome
数组进行映射,并使用其对应的曲目数据作为其属性返回一个 TrackCard
组件:
<Layout grid>{data?.tracksForHome?.map((track) => (<TrackCard key={track.id} track={track} />))}</Layout>
我们会立即发现 track
属性上存在错误!
为了了解上下文,我们来看看 GraphQL 代码生成器为我们生成的 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
组件而漂亮地显示出来。太棒了!
包装查询结果
刷新浏览器时,你可能注意到了,因为我们返回 loading
消息作为一个简单的字符串,我们 不 暂时显示组件的整个布局和导航栏,因为它正在加载中( error
消息也存在同样的问题)。我们应该确保我们的 UI 行为在 查询 的所有阶段中保持一致。
这就是我们的 QueryResult
辅助组件派上用场的地方。这不是一个直接由 Apollo 库提供的组件。我们添加它来使用 查询 结果在整个应用程序中以一致、可预测的方式。
我们打开 components/query-result
。此组件将 useQuery
钩子的返回值作为道具。然后,它执行基本的条件逻辑来呈现一个旋转器、一条错误消息或其子组件:
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";
我们现在可以在该文件中删除处理 loading
和 error
状态的行,因为 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>);
刷新浏览器,在加载时我们得到一个漂亮的旋转器,然后我们的卡片出现了!
在所有这些代码之后, tracks.tsx
文件应该看起来像这样:
就这样!我们的主页填充了一组酷炫的轨道卡片,就像我们在最初的模型中布局的那样。
练习
创建一个 ListSpaceCats
查询,带有 spaceCats
查询字段及其 name
、age
和 missions
选择集。对于 missions
字段,选择 name
和 description
使用带有 SPACECATS
查询的 useQuery
挂钩,并从结果中解构 loading
、error
和 data
属性
useQuery
挂钩用于干什么?关键要点
-
useQuery
挂钩是 React 应用程序中执行查询的主要 API。 -
useQuery
挂钩返回一个包含loading
、error
和data
属性的对象,我们可以使用这些属性来确定 UI 中的元素。
接下来
我们的主页看起来不错,但我们无处可去:下一步,让我们探讨如何设置我们的应用程序以仅显示一个 一个曲目对象。
分享一下你对此课程的问题和评论
本课程目前正在
您需要一个 GitHub 帐户才能在下面发帖。还没有一个? 改为在我们的 Odyssey 论坛中发帖。