GraphQL 入门教程
概述
GraphQL 是由 Facebook 开发并开源的,已在 Facebook 内部多个项目中使用,下图总结了 GraphQL 涉及的技术要点:
提到 GraphQL,大家自然而然会提起 RESTful api。下面对比一下 RESTful api 和 GraphQL 的优缺点。
优点:
- 声明式的接口获取 RESTful api 返回的字段冗余,当多个终端共用接口时,尤其明显。GraphQL 可精准的返回所需的数据结果,减少数据传输大小。
- 嵌套复杂数据仅需一次调用 RESTful 对于嵌套的复杂数据需要多次调用,而 GraphQL 只需要一次。
- 愉快地前后端联调效率 REST 每次新加字段,需频繁沟通,且需借助 swagger 生成接口文档,GraphQL 自动生成标准文档。
如果上面的优点看不懂,没关系,我们来举一个例子,以加深理解:
服务端 getUser
接口返回了 id
, name
信息,但是另外一个场景,需要额外多返回几个字段,比如 email
,adress
等。此时服务端要怎么做?有以下三种做法:
- 新开一个接口,返回所需要的所有字段
- 请求增加一个
type
,用于区分场景,服务端根据不同type
返回不同的字段 - 不管三七二十一,在原有接口上增加多的字段。
如果只是 1 个,2 个场景还好,但如果后期有 n 个场景,需要返回非常多的字段,这不仅会浪费带宽,客户端数据解析也会影响响应时间,从而影响用户体验。那让后台新增一个接口可以吗?当然可以,可是这样后台需要额外维护这种“业务逻辑”。
操作类型
GraphQL 提供了以下几种操作:
- 查询(query)
- 更新(mutation)
- 订阅(subscription)
GraphQL 实战
我一直提倡,刚开始学习一门新的技术,别看太多文档,先用它的 api,快速做一个 demo,跑起来之后。再开始系统学习,这样效率是最高的。所以,下面我们会实战来做一个 GraphQL .
跟着官方文档简单快速创建一个栗子。
初始化项目
1 | $ mkdir graphql-server-example |
定义 GraphQL schema
打开 index.js
,输入以下代码:
1 | const { ApolloServer, gql } = require('apollo-server'); |
定义 resolver
我们在上面的 index.js
文件后面,继续输入以下代码:
1 | const resolvers = { |
创建一个 ApolloServer
实例
我们在上面的 index.js
文件后面,继续添加代码:
1 | const server = new ApolloServer({ typeDefs, resolvers }); |
然后在命令行中运行 index.js
:
1 | $ node index.js |
好了,就是这么简单,一个 graphql 就写好了。实际中的项目可能使用 egg 或者 koa 或者 express。本质的思想是一样的,都是先定义 GraphQL schema
,再定义 resolver
,resolver
这里从不同地方取数,再之后就是传递 schema
和 resolver
,创建实例。
GraphiQL 工具
上面的代码运行起来了,要去哪里调用?如果是用 RESTful api ,我们会用 postman 来测试接口是否可以跑通。同样的,GraphQL 可以用 GraphiQL 来测试。
除了可以用来测试接口之后,它还是一个文档工具,使用 GraphiQL 可以很容易地让人感受到“代码即文档”的快乐。
你可以在这里查看测试的历史:
Schemas
从上面的例子看出,要先定义 Schemas,那我们就来学习下 Schemas。如果你使用过 Typescript ,会发现它的类型跟 Typescript 特别的类似。
基本类型
基本类型包括:
- Int
- Float
- String
- Boolean
- ID (唯一标识符)
其他类型
- enum, Date, interface
- !(不可为 null) 可通过 Union types 或 implements 扩展上面的类型。
2 个特殊类型
查询(query)和变更类型(mutation)
自定义类型
请参考官方文档。
Resolver
我们可以简单地理解成,针对我们暴露的接口,调用相应的方法去取数返回。resolver 的解析规则是,从外到内依次处理查询块,为每一个查询块执行对应的 resolver 函数,并传递外层调用返回的结果作为第一个参数,也就是下面代码中的 obj 。
resolver 函数它接收 4 个参数:
1 | fieldName(obj, args, context, info) { |
同样的,我们直接来看一个例子:在 index.js
中修改对应的 Schemas
和 Resolver
:
1 | const books = [ |
由于这个 resolver
函数第一个参数是传递外层调用的返回结果,这里我们没有嵌套 resolver
,所以我们直接用第二个参数 id
获取前端传入的参数。
更多内容查看 resolver 文档。
然后我们在 http://127.0.0.1:4000/graphql 或者在客户端 GraphiQL 中测试:
已经成功找到对应 id 的数据了,但是这里的 id 是写死的,我们说 graphql 最大的好处是声明式获取,那如何把 id 变成一个变量,让外部传入?
1 | query getBook($id: ID!) { |
给返回的值设置别名:
1 | query getBook($id: ID!) { |
从上图可以得知,接口正常返回。
进阶
在实际项目中,我们会将数据库 ,dataloaders 注入到 context 中,方便所有 resolver 调用。
Dataloader
Dataloader 是 facebook 搞的一个 js 库,可以大幅降低数据库的访问频次,从而降低系统负载,经常在 Graphql 场景中使用。通过使用 dataloader,数据库的访问频次可以指数级别下降。
dataloader 是如何工作的呢,可以看下图:
对于 User 表的多次访问,通过 dataloader 去取,会自动合并为一个请求。dataloader 之所以可以实现这样的能力,是因为他把每一次数据请求,都推迟到 node 的 Next Tick 后集中批处理运行,这样就可以对请求进行加工合并。
全文完,希望对你了解 GraphQL 有所帮助。