接上篇 —— Apollo 入门引导(二):连接数据源 —— 继续翻译 Apollo 的官网入门引导。
学习 GraphQL 的查询是如何获取数据的。
Apollo 入门引导 - 目录:
完成时间:15 分钟
前一节已经设计了 schema 并配置了数据源,但是服务不知道如何使用其数据源来填充 schema 字段。为了解决这个问题,接下来将定义一个解析器(resolver)集合。
解析器的功能是负责为 schema 中的字段填充数据。每当客户端查询特定字段时,该字段的解析器都会从适当的数据源中获取请求的数据。
解析器函数返回以下之一:
- 解析器对应 schema 字段所需的类型的数据(字符串,整数,对象等)
- 满足所需类型数据的期约(promise)
解析器函数签名
在开始编写解析器之前,先介绍一下解析器函数的签名是什么样的。解析器函数接受四个可选参数:
1 | fieldName: (parent, args, context, info) => data; |
参数 | 描述 |
---|---|
parent |
这是该字段的父级的解析器的返回值(父级解析器始终在其子字段的子级解析器之前执行)。 |
args |
该对象包含为此字段提供的所有GraphQL 参数。 |
context |
该对象在执行特定操作的所有解析器之间共享。使用此参数可以共享每个操作的状态,例如身份验证信息和对数据源的访问。 |
info |
其中包含有关操作执行状态的信息(仅在高级情况下使用) |
在这四个参数中,我们定义的解析器将主要使用 context
参数。它使我们的解析器可以共享 LaunchAPI
和 UserAPI
数据源实例。要了解其工作原理,下面就开始创建一些解析器。
定义顶级解析器
如上所述,父字段的解析器始终在其子字段的子解析器之前执行。因此,先从一些顶级字段的解析器开始定义:Query
类型。
正如 src/schema.js
所示,我们 schema 的 Query
类型定义了三个字段:launches
、 launch
和 me
。要为这些字段定义解析器,请打开 src/resolvers.js
并粘贴以下代码:
1 | module.exports = { |
如该代码所示,我们在映射(map)中定义了解析器,其中映射的键对应于 schema 的类型(Query
)和字段(launches
、launch
、me
)。
关于上面的函数参数:
- 所有三个解析器函数均将其第一个参数(
parent
)分配给变量_
,以表示用不到该值。 - 出于相同的原因,
launches
和me
函数将其 第二个 参数(args
)分配给__
。- (
launch
函数都用到了args
参数,因为 schema 的launch
字段带有id
参数。)
- (
- 三个解析器函数都用到了第三个参数(
context
)。具体来说,将其解构以访问之前定义的dataSources
。 - 三个解析器函数都没用到第四个参数(
info
),所以也不需要包含它。
如你所见,这些解析器函数都很短!原因是它们所依赖的大多数逻辑是 LaunchAPI
和 UserAPI
数据源定义的。使解析器变短是最佳实践,使你可以安全地重构支持逻辑,同时减少破坏 API 的可能性。
将解析器添加到 Apollo 服务
现在我们有了一些解析器,将它们添加到服务中。将 highlight 行添加到 src / index.js
中:
1 | const { ApolloServer } = require('apollo-server'); |
通过像这样向 Apollo 服务提供解析器映射,它自己就知道如何根据需要调用解析器函数来完成传入的查询。
运行测试查询
下面在服务上运行测试查询!使用 npm start
启动它,并打开先前探索你的 schema章节提供的工具:
- [Apollo Studio]中的 Explorer(https://studio.apollographql.com/dev)
- 位于
localhost:4000
的 GraphQL Playground
将以下查询粘贴到工具的编辑器面板中:
1 | # 在之后的教程中将会学习查询结构的更多信息 |
然后,单击 Run 按钮。服务的响应出现在右侧。看看响应对象的结构是如何与查询的结构匹配的?这种对应关系是 GraphQL 的基本特点。
现在尝试一个带有 GraphQL 参数 的测试查询。粘贴以下查询并运行:
1 | query GetLaunchById { |
这个查询返回 id
为 60
的 Launch
对象的详细信息。
通过这些工具,无需像上面的查询那样对参数进行硬编码,而是可以为操作定义 变量(variable)。下面是使用变量而不是60
的相同效果的查询:
1 | query GetLaunchById($id: ID!) { |
现在,将以下内容粘贴到工具的 Variables 面板中:
1 | { |
在继续操作之前,可以随意运行查询和设置变量。
定义其他解析器
你可能已经注意到,上面运行的测试查询包含了几个我们还没有编写解析器的字段。但是这些查询仍然可以成功运行!这是因为 Apollo 服务为没有自定义解析器的字段定义了一个默认解析器。
默认的解析器函数使用以下逻辑:
1 | graph TB; |
对于 schema 的大多数(但不是全部)字段,默认解析器完全可以实现想要的功能。接下来为 Mission.missionPatch
的 schema 字段定义一个自定义解析器。
该字段具有以下定义:
1 | # 不需要复制这段代码 |
Mission.missionPatch
的解析器应返回不同的值,具体取决于查询的 size
参数指定的是 LARGE
还是 SMALL
。
在 Query
属性下方的 src/resolvers.js
中,将以下内容添加到解析器映射中:
1 | // Query: { |
这个解析器从 mission
获取一个大的或小的徽章,这是 schema 的 父 字段Launch.mission
默认解析器返回的对象。
现在,我们知道了如何为 Query
之外的类型添加解析器,继续为 Launch
和 User
类型的字段添加解析器。在Mission
下方将以下内容添加到解析器映射中:
1 | // Mission: { |
你可能想知道服务在调用 getLaunchIDsByUser
之类的函数时如何知道当前用户的身份。目前还不能知道!将在下一章中解决该问题。
分页结果
当前,Query.launches
返回一长串 Launch
对象。这通常比客户端一次需要的信息冗余太多,并且获取大量数据可能速度会很慢。可以通过实现分页来改善该字段的性能。
分页可确保服务分小块发送数据。建议对带编号的页面进行基于游标的分页(cursor-based pagination),因为它消除了跳过一条或多次显示同一条的可能性。在基于游标的分页中,常量指针(或游标)用于在获取下一组结果时,跟踪数据集的起始位置。
下面来设置基于游标的分页。在 src/schema.js
中,更新 Query.launches
以匹配以下内容,并添加一个名为LaunchConnection
的新类型,如下所示:
1 | type Query { |
现在,Query.launches
接受两个参数(pageSize
和 after
)并返回一个 LaunchConnection
对象。LaunchConnection
包括:
launches
列表(查询请求到的实际数据)cursor
,“游标”指示数据集中当前位置hasMore
,布尔值,指示数据集是否除launches
中包含的项之外还有更多项
打开 src/utils.js
并查看 paginateResults
函数。这是用于从服务分页数据的辅助函数。
现在,来更新解析器函数以适应分页。导入 paginateResults
并用以下代码替换 src/resolvers.js
中的 launches
解析器函数:
1 | const { paginateResults } = require('./utils'); |
测试一下刚刚实现的基于游标的分页。使用 npm start
重新启动服务,并在 Explorer / GraphQL Playground 中运行以下查询:
1 | query GetLaunches { |
由于我们实现了分页,服务应该只返回三个发射而不是完整列表。
这样就完成了 schema 查询的解析器!接下来,为它的变更编写解析器。
前端记事本,不定期更新,欢迎关注!