查询中接受的主体参数_Apollo入门引导(三):编写查询解析器
接上篇 —— Apollo 入门引导(二):连接数据源 —— 继续翻译 Apollo 的官网入门引导。
学习 GraphQL 的查询是如何获取数据的。
Apollo 入门引导 - 目录:
- 介绍
- 构建 schema
- 连接数据源
- 编写查询解析器
- 编写变更解析器
- 连接 Apollo Studio
- 创建 Apollo 客户端
- 通过查询获取数据
- 通过变更修改数据
- 管理本地状态
完成时间:15 分钟
前一节已经设计了 schema 并配置了数据源,但是服务不知道如何使用其数据源来填充 schema 字段。为了解决这个问题,接下来将定义一个解析器(resolver)集合。
**解析器的功能是负责为 schema 中的字段填充数据。**每当客户端查询特定字段时,该字段的解析器都会从适当的数据源中获取请求的数据。
解析器函数返回以下之一:
- 解析器对应 schema 字段所需的类型的数据(字符串,整数,对象等)
- 满足所需类型数据的期约(promise)
解析器函数签名
在开始编写解析器之前,先介绍一下解析器函数的签名是什么样的。解析器函数接受四个可选参数:
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
并粘贴以下代码:
module.exports = { Query: { launches: (_, __, { dataSources }) => dataSources.launchAPI.getAllLaunches(), launch: (_, { id }, { dataSources }) => dataSources.launchAPI.getLaunchById({ launchId: id }), me: (_, __, { dataSources }) => dataSources.userAPI.findOrCreateUser(), },};
如该代码所示,我们在**映射(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
中:
const { ApolloServer } = require('apollo-server');const typeDefs = require('./schema');const { createStore } = require('./utils');const resolvers = require('./resolvers'); // highlight-line
const LaunchAPI = require('./datasources/launch');const UserAPI = require('./datasources/user');
const store = createStore();
const server = new ApolloServer({ typeDefs, resolvers, // highlight-line dataSources: () => ({ launchAPI: new LaunchAPI(), userAPI: new UserAPI({ store }), }),});
server.listen().then(({ url }) => { console.log(`? Server ready at ${url}`);});
通过像这样向 Apollo 服务提供解析器映射,它自己就知道如何根据需要调用解析器函数来完成传入的查询。
运行测试查询
下面在服务上运行测试查询!使用 npm start
启动它,并打开先前探索你的 schema章节提供的工具:
- [Apollo Studio]中的 Explorer(https://studio.apollographql.com/dev)
- 位于
localhost:4000
的 GraphQL Playground
将以下查询粘贴到工具的编辑器面板中:
# 在之后的教程中将会学习查询结构的更多信息query GetLaunches { launches { id mission { name } }}
然后,单击 Run 按钮。服务的响应出现在右侧。看看响应对象的结构是如何与查询的结构匹配的?这种对应关系是 GraphQL 的基本特点。
现在尝试一个带有 GraphQL 参数 的测试查询。粘贴以下查询并运行:
query GetLaunchById { launch(id: 60) { id rocket { id type } }}
这个查询返回 id
为 60
的 Launch
对象的详细信息。
通过这些工具,无需像上面的查询那样对参数进行硬编码,而是可以为操作定义 变量(variable)。下面是使用变量而不是60
的相同效果的查询:
query GetLaunchById($id: ID!) { launch(id: $id) { id rocket { id type } }}
现在,将以下内容粘贴到工具的 Variables 面板中:
{ "id": 60}
在继续操作之前,可以随意运行查询和设置变量。
定义其他解析器
你可能已经注意到,上面运行的测试查询包含了几个我们还没有编写解析器的字段。但是这些查询仍然可以成功运行!这是因为 Apollo 服务为没有自定义解析器的字段定义了一个默认解析器。
默认的解析器函数使用以下逻辑:
graph TB; parent("Does the parent argument have aproperty with this resolver's exact name?"); parent--No-->null("Return undefined"); parent--Yes-->function("Is that property's value a function?"); function--No-->return("Return the property's value"); function--Yes-->call("Call the function andreturn its return value");
对于 schema 的大多数(但不是全部)字段,默认解析器完全可以实现想要的功能。接下来为 Mission.missionPatch
的 schema 字段定义一个自定义解析器。
该字段具有以下定义:
# 不需要复制这段代码type Mission { # 其他字段的定义... missionPatch(size: PatchSize): String}
Mission.missionPatch
的解析器应返回不同的值,具体取决于查询的 size
参数指定的是 LARGE
还是 SMALL
。
在 Query
属性下方的 src/resolvers.js
中,将以下内容添加到解析器映射中:
// Query: {// ...// },Mission: { // 默认的size为 'LARGE' missionPatch: (mission, { size } = { size: 'LARGE' }) => { return size === 'SMALL' ? mission.missionPatchSmall : mission.missionPatchLarge; },},
这个解析器从 mission
获取一个大的或小的徽章,这是 schema 的 父 字段Launch.mission
默认解析器返回的对象。
现在,我们知道了如何为 Query
之外的类型添加解析器,继续为 Launch
和 User
类型的字段添加解析器。在Mission
下方将以下内容添加到解析器映射中:
// Mission: {// ...// },Launch: { isBooked: async (launch, _, { dataSources }) => dataSources.userAPI.isBookedOnLaunch({ launchId: launch.id }),},User: { trips: async (_, __, { dataSources }) => { // 通过user获得发射的id数组 const launchIds = await dataSources.userAPI.getLaunchIdsByUser();
if (!launchIds.length) return [];
// 通过id数组来查询launch数组 return ( dataSources.launchAPI.getLaunchesByIds({ launchIds, }) || [] ); },},
你可能想知道服务在调用 getLaunchIDsByUser
之类的函数时如何知道当前用户的身份。目前还不能知道!将在下一章中解决该问题。
分页结果
当前,Query.launches
返回一长串 Launch
对象。这通常比客户端一次需要的信息冗余太多,并且获取大量数据可能速度会很慢。可以通过实现分页来改善该字段的性能。
分页可确保服务分小块发送数据。建议对带编号的页面进行基于游标的分页(cursor-based pagination),因为它消除了跳过一条或多次显示同一条的可能性。在基于游标的分页中,常量指针(或游标)用于在获取下一组结果时,跟踪数据集的起始位置。
下面来设置基于游标的分页。在 src/schema.js
中,更新 Query.launches
以匹配以下内容,并添加一个名为LaunchConnection
的新类型,如下所示:
type Query { launches( # 替换当前的launches查询 """ 要展示的结果数量,必须>= 1. Default = 20 """ pageSize: Int """ 如果在这里添加了一个游标,只返回游标_之后_的内容 """ after: String ): LaunchConnection! launch(id: ID!): Launch me: User}
"""launches列表周围的简单包装,其中包含到列表中的最后一项的游标。将此游标传到launches查询,以在这些之后获取结果。"""type LaunchConnection { # 作为一个可选类型,添加在Query类型下面 cursor: String! hasMore: Boolean! launches: [Launch]!}
现在,Query.launches
接受两个参数(pageSize
和 after
)并返回一个 LaunchConnection
对象。LaunchConnection
包括:
launches
列表(查询请求到的实际数据)cursor
,“游标”指示数据集中当前位置hasMore
,布尔值,指示数据集是否除launches
中包含的项之外还有更多项
打开 src/utils.js
并查看 paginateResults
函数。这是用于从服务分页数据的辅助函数。
现在,来更新解析器函数以适应分页。导入 paginateResults
并用以下代码替换 src/resolvers.js
中的 launches
解析器函数:
const { paginateResults } = require('./utils');
module.exports = { Query: { launches: async (_, { pageSize = 20, after }, { dataSources }) => { const allLaunches = await dataSources.launchAPI.getAllLaunches(); // 希望按时间倒序排列 allLaunches.reverse();
const launches = paginateResults({ after, pageSize, results: allLaunches, });
return { launches, cursor: launches.length ? launches[launches.length - 1].cursor : null, // 如果分页结果末尾的游标与_所有_结果中的最后一项相同,则再之后就没有结果 hasMore: launches.length ? launches[launches.length - 1].cursor !== allLaunches[allLaunches.length - 1].cursor : false, }; }, launch: (_, { id }, { dataSources }) => dataSources.launchAPI.getLaunchById({ launchId: id }), me: async (_, __, { dataSources }) => dataSources.userAPI.findOrCreateUser(), },};
测试一下刚刚实现的基于游标的分页。使用 npm start
重新启动服务,并在 Explorer / GraphQL Playground 中运行以下查询:
query GetLaunches { launches(pageSize: 3) { launches { id mission { name } } }}
由于我们实现了分页,服务应该只返回三个发射而不是完整列表。
这样就完成了 schema 查询的解析器!接下来,为它的变更编写解析器。
前端记事本,不定期更新,欢迎关注!
- 微信公众号:林景宜的记事本
- 博客:林景宜的记事本
- 掘金专栏:林景宜的记事本
- 知乎专栏:林景宜的记事本
- Github: MageeLin
查询中接受的主体参数_Apollo入门引导(三):编写查询解析器相关推荐
- 查询中接受的主体参数_【自动化/测开面试集锦系列】SQL查询
前言 select top n 形式的语句可以获取查询的前几个记录,但是 mysql没有此语法,mysql用limit来实现相关功能. LIMIT子句可以被用于强制 SELECT 语句返回指定的记录数 ...
- Jumony入门(三)初探解析器
首先介绍一下Jumony是什么,Jumony是一个.NET的开源项目,项目主页位于:http://jumony.codeplex.com/,采用LGPL协议发布. Jumony试图提供在传统Web开发 ...
- OpenGL入门(三)之着色器Shader
本系列文章为Learn OpenGL个人学习总结! OpenGL入门(一)之认识OpenGL和创建Window OpenGL入门(二)之渲染管线pipeline,VAO.VBO和EBO OpenGL入 ...
- 【SpringMVC入门】SpringMVC环境搭建、接收参数的几种方式、视图解析器、@ResponseBody
一.SpringMVC 简介 1.SpringMVC 中重要组件 1.1 DispatcherServlet: 前端控制器,接收所有请求(如果配置/不包含jsp) 1.2 HandlerMapping ...
- MySQL数据库入门(三)--- 查询、权限、join语法、外键、备份
MySQL数据库入门(三) 文章目录 MySQL数据库入门(三) @[toc] MySQL回顾 查询: 查看字段 权限管理 查询权限 授权查询 多表查询 join语法 外键 备份: 指定时间段备份: ...
- c语言中的函数可变参数列表相关的三个宏
在stdarg.h头文件中声明了一个类型va_list和3个与函数可变参数列表有关的宏:va_start.va_arg.va_end. #include<stdarg.h> //包含宏相关 ...
- oracle分页查询中的page,用简单的例子解释Oracle分页查询
什么是分页查询 分页查询就是把query到的结果集按页显示.比如一个结果集有1W行,每页按100条数据库.而你获取了第2页的结果集. 为什么要分页查询 如果一个结果集有几十W行,那么在一个页面上显示肯 ...
- 入门机器学习(三)--课后作业解析-线性回归(Python实现)
相关资料: 线性回归Python实现源码与数据集下载 吴恩达机器学习课件与课后习题下载 1. 单变量线性回归 在本练习的这一部分中, 您将使用一个变量实现线性回归, 以预测食品卡车的道具.假设你是一个 ...
- 读书笔记:SQL 查询中的SQL*Plus 替换变量(DEFINE变量)和参数
本文为"SQL*Plus 替换变量 - 在 SQL 查询中定义变量和参数"的读书笔记. 此文主要是讲替换变量,也称为DEFINE变量,但也涉及了绑定变量和SQL Plus系统变量. ...
最新文章
- python安装文件乱码_python输出到文件乱码如何解决
- 少儿python编程培训-无锡江阴少儿Python编程培训一对一
- python与数学的故事_我与数学的故事作文800字
- 网站等保测评针对服务器,互联互通测评知识分享之信息安全建设要点
- 论文浅尝 | 知识图谱相关实体搜索
- 【算法分析与设计】汉诺塔问题
- 盘点黑客与程序员的十大区别
- 算法踩坑4-冒泡排序
- WriteFile API简介
- delphi 停电文本数据丢失_河南照片数据恢复怎么联系
- 计算机报考电子邮箱格式,电子邮箱格式怎么写
- php网页抓取浏览者手机号码_php 获取 手机浏览器的信息 获取手机号
- h, w = img.shape什么意思?
- 移植tas5707功放芯片驱动注意事项
- 19讲项目实战站内搜索页
- IOS开发-常用的第三方库
- adobe cs4系列套装及注册机下载
- 微电影宣传片制作步骤分享。
- 谷歌地图 图片保存_Google如何在地图上跟踪并保存您的一举一动
- 口碑极好的SAP PP培训视频, 21G, 52个视频