如何使用GraphQL-基础教程:核心概念

440 阅读7分钟

接上篇 —— 如何使用 GraphQL-基础教程:GraphQL 比 REST 的优点 —— 继续翻译How to GraphQL 系列教程

基础和进阶系列翻译已完成:

  1. 介绍
  2. GraphQL 比 REST 的优点
  3. 核心概念
  4. 架构
  5. 客户端
  6. 服务端
  7. 更多概念
  8. 工具和生态
  9. 安全
  10. 常见问题

在本章中,将学习 GraphQL 的一些基本语言构造。包括对定义 types 以及发送 queriesmutations 语法的第一印象。

模式定义语言(Schema Definition Language,SDL)

GraphQL 有自己的类型系统,该系统用于定义 API 的 schema。编写 schema 的语法称为模式定义语言(Schema Definition Language,SDL)。

这是一个示例,说明如何使用 SDL 定义一个简单的类型,称为 Person

type Person {
  name: String!
  age: Int!
}

此类型有两个 字段(fields),它们分别称为nameage,分别为 StringInt 类型。类型后面的 ! 表示此字段是 必需 的。

也可以表达类型之间的关系。在 博客 应用程序的示例中,Person 可以与 Post 关联:

type Post {
  title: String!
  author: Person!
}

相反,关系的另一端需要放在 Person 类型上:

type Person {
  name: String!
  age: Int!
  posts: [Post!]!
}

注意,由于在 PersonPost 之间创建了 一对多(one-to-many) 关系,因为 Person 上的 posts 字段实际上是 post 数组

带查询条件获取数据

使用 REST API 时,将从特定端点加载数据。每个端点都有一个明确定义的返回信息结构。这意味着客户端的数据要求实际上是在其连接的 URL 中 编码(encoded) 的。

GraphQL 中采用的方法完全不同。 GraphQL API 并不具有返回固定数据结构的多个端点,而是通常仅公开 一个端点。之所以有效的原因是返回的数据的结构并不固定。相反,它是完全灵活的,可以让客户端决定实际需要什么数据。

这意味着客户端需要向服务器发送更多的 信息 来表达其数据需求 ,此信息称为 query(查询)

基本查询

让我们看一个客户端可以发送到服务端的查询示例:

{
  allPersons {
    name
  }
}

该查询中的 allPersons 字段称为查询的 根字段(root field)。根字段之后的所有内容都称为查询的 负载(payload) 。该查询的负载中指定的唯一字段是 name

该查询将返回当前存储在数据库中的所有人员的列表。下面是一个示例响应(response):

{
  "allPersons": [
    { "name": "Johnny" },
    { "name": "Sarah" },
    { "name": "Alice" }
  ]
}

请注意,每个人在响应中仅具有 name,服务端不会返回 age。这就是因为 name 是查询中唯一指定的字段。

如果客户端还需要人员的 age,则只需稍微调整查询,并将新字段包括在查询的负载中即可:

{
  allPersons {
    name
    age
  }
}

GraphQL 的主要优点之一是可以自然地查询*嵌套(nested)*信息。例如,如果想加载 Person 写的所有 posts,则可以简单地按照类型的结构来请求此信息:

{
  allPersons {
    name
    age
    posts {
      title
    }
  }
}

带参数查询

在 GraphQL 中,可以在 schema 中指定每个 字段 ,使其具有任意多个参数。例如,allPersons 字段可以具有 last 参数,使响应中仅返回特定数目的人员。下面是对应查询的结构:

{
  allPersons(last: 2) {
    name
  }
}

使用 mutation 写数据

除了从服务端请求信息之外,大多数应用程序还需要某种方式来更改当前存储在服务端中的数据。使用 GraphQL,这些更改是使用 变更(mutation) 进行的。通常存在三种 变更

  • 创建新的数据
  • 修改已存在的数据
  • 删除已存在的数据

变更遵循与查询相同的语法结构,但是它们始终需要以 mutation 关键字开头。下面是一个有关如何创建新的 Person 的示例:

mutation {
  createPerson(name: "Bob", age: 36) {
    name
    age
  }
}

注意,与我们之前写的查询类似,变更也有一个 根字段。在本例中为 createPerson。我们也已经了解了字段参数的概念。在本例中 createPerson 字段采用两个参数来指定新人员的 nameage

像查询一样,我们还可以为变更指定负载,在变更中我们可以请求新的 Person 对象的不同属性。在本例中,我们请求了nameage,尽管在本例中并没有多大帮助,因为将它们传递给 mutation 时,我们显然已经知道它们是什么。但是,发送 mutation 时也可以查询信息是一个非常强大的特性,使你可以在一次往返中从服务端检索新信息!

上述变更的服务端响应如下所示:

"createPerson": {
  "name": "Bob",
  "age": 36,
}

经常会发现, GraphQL 类型具有唯一的 ID ,它们是在创建新对象时由服务端生成的。扩展一下之前的Person类型,可以添加一个id,如下所示:

type Person {
  id: ID!
  name: String!
  age: Int!
}

现在,当创建一个新的 Person 时,因为这是客户端之前没有 id,所以可以直接在变更的负载中请求 id

mutation {
  createPerson(name: "Alice", age: 36) {
    id
  }
}

使用订阅来实时更新

当今,许多应用的另一个重要要求是与服务端建立 “实时” 连接,以便立即获取重要事件。对于这种情况,GraphQL 提供了 订阅(subscription) 的概念。

客户端订阅事件时,它将启动并保持与服务器的稳定连接。每当该特定事件实际发生时,服务端就会将相应的数据推送到客户端。与遵循典型的“请求-响应循环”的查询和变更不同,订阅表示发送到客户端的数据的 流(stream)

订阅使用与查询和变更相同的语法编写。下面是一个示例,订阅了 Person 类型发生的事件:

subscription {
  newPerson {
    name
    age
  }
}

客户端将此订阅发送到服务器后,它们之间将打开连接。然后,每当执行一个新的变更以创建一个新的 Person 时,服务端就会将有关此人员的信息发送给客户端:

{
  "newPerson": {
    "name": "Jane",
    "age": 23
  }
}

定义一个 Schema

现在已经对查询,变更和订阅有了基本的了解,下面将它们放在一起,并学习如何编写 schema 以执行到目前为止所看到的示例。

schema 是使用 GraphQL API 时最重要的概念之一。它指定 API 的功能并定义客户端如何请求数据。通常将其视为服务端和客户端之间的 合约

通常,schema 只是 GraphQL 类型的集合。但是,在为 API 编写 schema 时,有一些特殊的 根(root) 类型:

type Query { ... }
type Mutation { ... }
type Subscription { ... }

QueryMutationSubscription 类型是客户端发送的请求的 入口点(entry point) 。为了实现之前看到的 allPersons 查询,必须将 Query类型编写如下:

type Query {
  allPersons: [Person!]!
}

allPersons 被称为 API 的 根字段(root field)。再考虑到 allPersons 字段添加 last 参数的示例,我们必须编写 Query,如下所示:

type Query {
  allPersons(last: Int): [Person!]!
}

同样,对于 createPerson 变更,必须在 Mutation 类型中添加一个根字段:

type Mutation {
  createPerson(name: String!, age: Int!): Person!
}

注意,这个根字段也接受两个参数,即新的 Personnameage

最后,对于订阅,我们必须添加 newPerson 根字段:

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!
}

学习更多

要了解更多有关 GraphQL 核心概念的内容,可以查看以下系列文章:


前端记事本,不定期更新,欢迎关注!