接上篇 —— Apollo 入门引导(二):连接数据源 —— 继续翻译 Apollo 的官网入门引导。

学习 GraphQL 的查询是如何获取数据的。

Apollo 入门引导 - 目录:

  1. 介绍
  2. 构建 schema
  3. 连接数据源
  4. 编写查询解析器
  5. 编写变更解析器
  6. 连接 Apollo Studio
  7. 创建 Apollo 客户端
  8. 通过查询获取数据
  9. 通过变更修改数据
  10. 管理本地状态

完成时间:15 分钟

前一节已经设计了 schema 并配置了数据源,但是服务不知道如何使用其数据源来填充 schema 字段。为了解决这个问题,接下来将定义一个解析器(resolver)集合。

**解析器的功能是负责为 schema 中的字段填充数据。**每当客户端查询特定字段时,该字段的解析器都会从适当的数据源中获取请求的数据。

解析器函数返回以下之一:

  • 解析器对应 schema 字段所需的类型的数据(字符串,整数,对象等)
  • 满足所需类型数据的期约(promise)

解析器函数签名

在开始编写解析器之前,先介绍一下解析器函数的签名是什么样的。解析器函数接受四个可选参数:

fieldName: (parent, args, context, info) => data;
参数 描述
parent 这是该字段的父级的解析器的返回值(父级解析器始终在其子字段的子级解析器之前执行)。
args 该对象包含为此字段提供的所有GraphQL 参数。
context 该对象在执行特定操作的所有解析器之间共享。使用此参数可以共享每个操作的状态,例如身份验证信息和对数据源的访问。
info 其中包含有关操作执行状态的信息(仅在高级情况下使用)

在这四个参数中,我们定义的解析器将主要使用 context 参数。它使我们的解析器可以共享 LaunchAPIUserAPI 数据源实例。要了解其工作原理,下面就开始创建一些解析器。

定义顶级解析器

如上所述,父字段的解析器始终在其子字段的子解析器之前执行。因此,先从一些顶级字段的解析器开始定义:Query 类型。

正如 src/schema.js 所示,我们 schema 的 Query 类型定义了三个字段:launcheslaunchme。要为这些字段定义解析器,请打开 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)和字段(launcheslaunchme)。

关于上面的函数参数:

  • 所有三个解析器函数均将其第一个参数(parent)分配给变量 _,以表示用不到该值。
  • 出于相同的原因,launchesme 函数将其 第二个 参数(args)分配给__
    • (launch函数都用到了 args 参数,因为 schema 的 launch 字段带有 id 参数。)
  • 三个解析器函数都用到了第三个参数(context)。具体来说,将其解构以访问之前定义的 dataSources
  • 三个解析器函数都没用到第四个参数(info),所以也不需要包含它。

如你所见,这些解析器函数都很短!原因是它们所依赖的大多数逻辑是 LaunchAPIUserAPI 数据源定义的。使解析器变短是最佳实践,使你可以安全地重构支持逻辑,同时减少破坏 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    }  }}

这个查询返回 id60Launch 对象的详细信息。

通过这些工具,无需像上面的查询那样对参数进行硬编码,而是可以为操作定义 变量(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 之外的类型添加解析器,继续为 LaunchUser 类型的字段添加解析器。在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 接受两个参数(pageSizeafter)并返回一个 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入门引导(三):编写查询解析器相关推荐

  1. 查询中接受的主体参数_【自动化/测开面试集锦系列】SQL查询

    前言 select top n 形式的语句可以获取查询的前几个记录,但是 mysql没有此语法,mysql用limit来实现相关功能. LIMIT子句可以被用于强制 SELECT 语句返回指定的记录数 ...

  2. Jumony入门(三)初探解析器

    首先介绍一下Jumony是什么,Jumony是一个.NET的开源项目,项目主页位于:http://jumony.codeplex.com/,采用LGPL协议发布. Jumony试图提供在传统Web开发 ...

  3. OpenGL入门(三)之着色器Shader

    本系列文章为Learn OpenGL个人学习总结! OpenGL入门(一)之认识OpenGL和创建Window OpenGL入门(二)之渲染管线pipeline,VAO.VBO和EBO OpenGL入 ...

  4. 【SpringMVC入门】SpringMVC环境搭建、接收参数的几种方式、视图解析器、@ResponseBody

    一.SpringMVC 简介 1.SpringMVC 中重要组件 1.1 DispatcherServlet: 前端控制器,接收所有请求(如果配置/不包含jsp) 1.2 HandlerMapping ...

  5. MySQL数据库入门(三)--- 查询、权限、join语法、外键、备份

    MySQL数据库入门(三) 文章目录 MySQL数据库入门(三) @[toc] MySQL回顾 查询: 查看字段 权限管理 查询权限 授权查询 多表查询 join语法 外键 备份: 指定时间段备份: ...

  6. c语言中的函数可变参数列表相关的三个宏

    在stdarg.h头文件中声明了一个类型va_list和3个与函数可变参数列表有关的宏:va_start.va_arg.va_end. #include<stdarg.h> //包含宏相关 ...

  7. oracle分页查询中的page,用简单的例子解释Oracle分页查询

    什么是分页查询 分页查询就是把query到的结果集按页显示.比如一个结果集有1W行,每页按100条数据库.而你获取了第2页的结果集. 为什么要分页查询 如果一个结果集有几十W行,那么在一个页面上显示肯 ...

  8. 入门机器学习(三)--课后作业解析-线性回归(Python实现)

    相关资料: 线性回归Python实现源码与数据集下载 吴恩达机器学习课件与课后习题下载 1. 单变量线性回归 在本练习的这一部分中, 您将使用一个变量实现线性回归, 以预测食品卡车的道具.假设你是一个 ...

  9. 读书笔记:SQL 查询中的SQL*Plus 替换变量(DEFINE变量)和参数

    本文为"SQL*Plus 替换变量 - 在 SQL 查询中定义变量和参数"的读书笔记. 此文主要是讲替换变量,也称为DEFINE变量,但也涉及了绑定变量和SQL Plus系统变量. ...

最新文章

  1. python安装文件乱码_python输出到文件乱码如何解决
  2. 少儿python编程培训-无锡江阴少儿Python编程培训一对一
  3. python与数学的故事_我与数学的故事作文800字
  4. 网站等保测评针对服务器,互联互通测评知识分享之信息安全建设要点
  5. 论文浅尝 | 知识图谱相关实体搜索
  6. 【算法分析与设计】汉诺塔问题
  7. 盘点黑客与程序员的十大区别
  8. 算法踩坑4-冒泡排序
  9. WriteFile API简介
  10. delphi 停电文本数据丢失_河南照片数据恢复怎么联系
  11. 计算机报考电子邮箱格式,电子邮箱格式怎么写
  12. php网页抓取浏览者手机号码_php 获取 手机浏览器的信息 获取手机号
  13. h, w = img.shape什么意思?
  14. 移植tas5707功放芯片驱动注意事项
  15. 19讲项目实战站内搜索页
  16. IOS开发-常用的第三方库
  17. adobe cs4系列套装及注册机下载
  18. 微电影宣传片制作步骤分享。
  19. 谷歌地图 图片保存_Google如何在地图上跟踪并保存您的一举一动
  20. 口碑极好的SAP PP培训视频, 21G, 52个视频

热门文章

  1. 原来sync.Once还能这么用
  2. UserDetailsService详解
  3. Spring Boot注解详解
  4. [转] 2018年冬流感通知
  5. 记录一次跨国笔试经过
  6. 开源 java CMS - FreeCMS2.6 站点设置
  7. 身为“月光族”的电视选择之道
  8. js中的date操作
  9. 操作系统(10)-虚拟内存
  10. Windows 安装 sbt