2. 让我们学习GraphQL
本课程是我们为 GraphQL
社区贡献的高质量学习经验。课程遵循与BulletProof Meteor
(超过15000个开发者通过它来学习构建高性能Meteor
应用)相同的课程结构。
课程结构
我们逐一介绍GraphQL
关键知识点。我们偶尔会提出一些问题来验证您学习成果。您也可以就您的困惑提出问题。
您可以跳过这些问题,但是我们建议您试着回答它们,因为它们非常简单,我们也会维护一个积分榜,在这里您可以赢得一些奖品。
需要具备的知识
我们会先讲基础,您可以不了解GraphQL
,但您需要有 js
的基础。我们会应用 ES2015
的语法,您可以从 这里 学习它。
当您学写
GraphQL schema
的时候,我们也会使用node
。即使您没有任何node
经验, 您也可以学习这些课程(确保您的电脑上安装了NodeJS
)。
对于一些课程,我们可能需要克隆 git
仓库来执行一些任务。所以,我们假设您之前已有使用git
的经历。如果没有,您可以在这里学习。
GraphQL
沙盒
在前几节课,我们会使用GraphQL
查询语言。为此,我们将使用已经定义好的GraphQL Schema
。这是典型博客应用的schema
:
- 我们的博客有多篇文章.
- 每篇文章有一个作者和可能有的一些评论。
- 每个评论有一个评论者。
- 每个评论有多个回复。
为了和 schema
交互, 您可以使用GraphQL Sandbox
。它基于一个叫做GraphiQL
的开源GraphQL
编辑器。它内置了 文档 和 自动补全 的功能。本课程中将使用它来验证您学到的内容并完成相关任务。
因此,最好先看一下关于这个的 小视频,以便您尽快熟悉它。
课程会是有趣的
我们试图保持每节课的精美,所以你可以利用你的空闲时间去实践它。如果你喜欢任一的课程,不要忘了分享它并发表评论。
让我们开始吧!
核心概念
GraphQL 拥有自己的一套定义 API 结构的体系。编写API 结构的语法 我们称之为 Definition Language (SDL).
SDL GraphQL 中使用 SDL 来定义 API 的 schema
假设有一个 Person
类型,我们看看通过 SDL 怎么样来定义
type Person {
name: String!
age: Int!
}
name
age
是这个类型所包含的两个字段,数据类型分别为 String
Int
。 !
表示该字段是必须存在的
不同类型之间也可以存在关联关系,在博客系统这个例子中, Post
中会存在与之相关的 Person
。
type Post {
title: String!
author: Person!
}
反之 需要在 Person
中定义
type Person {
name: String!
age: Int!
posts: [Post!]!
}
由于上面的 posts
字段是一个数组 意味着 Person
和 Post
之间是一对多的关系
Query 使用 Query 来获取/读数据
如果是使用 Rest API 我们可以通过特定的服务URL获取数据。每个服务URL 都事先明确定义了该服务会返回的数据结构。也就是说客户端的数据需求是编码在它所发起请求的URL中,通过在 URL 中添加各种参数来实现对某次请求响应数据的过滤等操作。
GraphQL 中所使用的方法则截然不同,与其使用多个返回固定数据结构的服务URL, GraphQL 中只会暴露一个服务URL。由于返回的数据结构不是固定的,所以这种方式也是可行的。无独有偶,同性交友社区也存在同样设计逻辑的是 ⚡后端接口和文档自动化,前端(客户端) 定制返回JSON的数据和结构! http://apijson.cn 这样做的灵活性在于客户端可以按需访问数据,也就是说客户端发起请求的时候要额外发送一些信息来告知服务端它的数据需求-这里的额外信息我们称之为 Query 这类做法其实在医疗信息化行业还是不陌生的,我们卫计委区域卫生信息平台定义的一些 SOAP 接口就是类似讨论。
不带参数的 query
查询请求
{
allPersons {
name
}
}
示例响应
{
"allPersons": [
{ "name": "Johnny" },
{ "name": "Sarah" },
{ "name": "Alice" }
]
}
上面这个query 查询的是数据库中现存的所有 person 的姓名。由于请求中只要求了 name
这一个字段,服务器并没有返回 age
的信息。
allPersons
是 query 的根节点,根节点下面的所有称之为 query 的payload。上面的例子中 这个query的payload 只有 name
这一个字段。
如果查询请求中增加 age
字段
{
allPersons {
name
age
}
}
查询的响应会变成
{
"data": {
"allPersons": [
{
"name": "Johnny",
"age": 23
},
{
"name": "Sarah",
"age": 20
},
{
"name": "Alice",
"age": 20
}
]
}
}
GraphQL 的一大优点在于可以自然而然的查询嵌套信息,按照你类型中的定义,加载 Person
所撰写的所有 'posts'
{
allPersons {
name
age
posts {
title
}
}
}
响应为
{
"data": {
"allPersons": [
{
"name": "Johnny",
"age": 23,
"posts": [
{
"title": "GraphQL is awesome"
},
{
"title": "Relay is a powerful GraphQL Client"
}
]
},
{
"name": "Sarah",
"age": 20,
"posts": [
{
"title": "How to get started with React & GraphQL"
}
]
},
{
"name": "Alice",
"age": 20,
"posts": []
}
]
}
}
带参数的Query
如果 schema 中定义过了,GraphQL 中每个字段都可以有0-个参数。使用参数 last* 可以让服务器返回最近注册的2个person 例如
{
allPersons(last: 2) {
name
}
}
响应为
{
"data": {
"allPersons": [
{
"name": "Sarah"
},
{
"name": "Alice"
}
]
}
}
使用mutation 来回写数据
除了从服务器读数据,应用程序还需要对数据库中存储的数据做一些修改和变更。在 GraphQL 中这些变更我们使用 mutation 就可以实现,目前有三类 mutation:
- 创建新的数据
- 更新已有的数据
- 删除已有的数据
mutation 和 query 结构差不多,只不过都是以 mutation 打头的。创建一个 Person
的例子
mutation {
createPerson(name: "Bob", age: 36) {
name
age
}
}
服务器的响应
"createPerson": {
"name": "Bob",
"age": "36",
}
mutation 的根节点为 createPerson
,它包含了两个参数,分别为 name 和 age 的具体值。
和query一样的地方,我们在mutation中可以使用payload来查询Person对象的其他属性,在上面的例子中name``age
和我们传入的参数一样。但是能够在向服务器上插入数据的同时获取新的信息,我们可以在与服务器的一次交互中完成。
你会注意到每当创建一个新的对象服务器都会分配一个唯一的 ID,在之前 Person
的基础上新增一个 id
字段
type Person {
id: ID!
name: String!
age: Int!
}
改写前面的mutation 你可以在服务器创建对象后返回对象的id
mutation {
createPerson(name: "Alice", age: 36) {
id
}
}
使用 Subscriptions 实现实时更新
现如今很多应用程序都会与服务器建立一个实时链接来获取所关心事件的实时信息。针对这种场景,GraphQL 提供了 Subscriptions。
当客户端订阅了某类事件,它会初始化并保持与服务器的连接,每每发生了一个此类事件,服务器推送相应数据给客户端。与query mutation这种请求-响应模式不同,Subscriptions表示服务器主动推送数据给客户端。
假设我们要订阅 Person
的事件:
subscription {
newPerson {
name
age
}
}
响应
{
"newPerson": {
"name": "Jane",
"age": 23
}
}
schema定义
在有了上面对 query mutation subscription 的感性认识以后,我们看一下如何定义一个 schema。 schema 定义了 GraphQL API 所拥有的能力,以及客户端如何访问数据。你可以将其理解成服务器和客户端之间的契约。
一般而言,schema 就是一堆 GraphQL 类型的集合。在书写的时候,我们会定义一些根节点
type Query { ... }
type Mutation { ... }
type Subscription { ... }
Query, Mutation, and Subscription
是客户端所发出请求的根节点,要实现上面的 allPersons
query,如下定义 Query
即可
type Query {
allPersons: [Person!]!
}
allPersons
是 API的根节点,前面 allPersons 有一个 last 参数 ,那么该如下定义
type Query {
allPersons(last: Int): [Person!]!
}
createPerson 的定义如下
type Mutation {
createPerson(name: String!, age: Int!): Person!
}
type Subscription {
newPerson: Person!
}
一份完整的schema如下所示
type Query {
allPersons(last: Int): [Person!]!
}
type Mutation {
createPerson(name: String!, age: Int!): Person!
}
type Subscription {
newPerson: Person!
}
type Person {
name: String!
age: Int!
posts: [Post!]!
}
type Post {
title: String!
author: Person!
}