理解 GraphQL 类型系统

作者选择了自由开源基金作为为捐赠而写计划的一部分来接受捐赠。

介绍

GraphQL是促进前端与数据源通信的现代解决方案。GraphQL实现的所有细节和功能都在GraphQL模式中阐明。为了编写一个功能正常的GraphQL模式,您必须了解GraphQL类型系统

在本文中,您将了解GraphQL类型:五种内置标量类型,枚举列表和非空包装类型,对象类型,以及与它们配合使用的抽象接口联合类型。您将审查每种类型的示例,并学习如何使用它们来构建完整的GraphQL模式。

先决条件

为了充分利用本教程,您应该具备以下知识:

标量类型

GraphQL模式中的所有数据最终都会解析为各种标量类型,这些类型代表原始值。GraphQL响应可以表示为一棵树,而标量类型是树末端的叶子。响应中可以有多个嵌套级别,但最后一级始终会解析为标量(或枚举)类型。GraphQL带有五种内置标量类型:IntFloatStringBooleanID

Int

Int 是一个带符号的 32 位非小数数值。它是一个带符号(正数或负数)的整数,不包括小数。带符号 32 位整数的最大值是 2,147,483,647。这是用于数值数据的两种内置标量之一。

Float

A Float is a signed double-precision fractional value. It is a signed (positive or negative) number that contains a decimal point, such as 1.2. This is the other built-in scalar used for numerical data.

String

A String is a UTF-8 character sequence. The String type is used for any textual data. This can also include data like very large numbers. Most custom scalars will be types of string data.

Boolean

A Boolean is a true or false value.

ID

ID 是一个唯一标识符。这个值总是被序列化为一个字符串,即使这个 ID 是数字。一个 ID 类型可能常用一个 通用唯一标识符 (UUID) 来表示。

自定义标量

除了这些内置标量之外,scalar 关键字可用于定义自定义标量。您可以使用自定义标量创建具有额外服务器级验证的类型,例如 DateTimeUrl。以下是定义新 Date 类型的示例:

scalar Date

服务器将知道如何处理与此新类型的交互,使用 GraphQLScalarType

枚举类型

Enum 类型,也称为 Enumerator 类型,描述了一组可能的值。

如何使用 GraphQL 管理数据 系列的其他教程中使用幻想游戏 API 主题,您可能会为游戏角色的 JobSpecies 制作一个 enum,其中包含系统将接受的所有值。枚举是用 enum 关键字定义的,如下所示:

"The job class of the character."
enum Job {
  FIGHTER
  WIZARD
}

"The species or ancestry of the character."
enum Species {
  HUMAN
  ELF
  DWARF
}

这样,就保证了一个角色的JobFIGHTERWIZARD,永远不会意外地变成"purple"或其他随机字符串,如果你使用了String类型而不是自定义枚举的话,这种情况是可能发生的。按照约定,枚举通常以全大写字母编写。

枚举也可以用作参数的可接受值。例如,你可以创建一个Hand enum来表示武器是单手(像短剑)还是双手(像重型斧头),并使用它来确定是否可以装备一把或两把:

enum Hand {
  SINGLE
  DOUBLE
}

"A valiant weapon wielded by a fighter."
type Weapon {
  name: String!
  attack: Int
  range: Int
  hand: Hand
}

type Query {
  weapons(hand: Hand = SINGLE): [Weapon]
}

Hand enum已经声明了SINGLEDOUBLE作为值,weapons字段上的参数默认值为SINGLE,这意味着如果没有传递参数,它将退回到SINGLE

非空类型

你可能注意到,在内置标量列表中,nullundefined,许多语言都认为是原始类型的常见类型,不在其中。Null在GraphQL中确实存在,表示没有值。

所有GraphQL中的类型默认都是可空的,因此null对于任何类型都是有效的响应。为了使值为必需,必须将其转换为带有感叹号的GraphQL 非空类型。非空被定义为类型修饰符,这些类型用于修改其所指的类型。例如,String是可选(或可空)字符串,而String!是必需(或非空)字符串。

列表类型

A List type in GraphQL is another type modifier. Any type that is wrapped in square brackets ([]) becomes a List type, which is a collection that defines the type of each item in a list.

例如,定义为[Int]的类型将是Int类型的集合,而[String]将是String类型的集合。非空和列表可以一起使用,使类型既必需又定义为列表,例如[String]!

对象类型

如果GraphQL标量类型描述了层次结构GraphQL响应末端的“叶子”,那么对象类型描述了中间的“分支”,并且几乎在GraphQL模式中的每个地方都是对象类型。

对象由一系列命名字段(键)和每个字段将解析为的值类型组成。对象是用type关键字定义的。必须至少定义一个或多个字段,并且字段不能以两个下划线(__)开头,以避免与GraphQL内省系统冲突。

在GraphQL Fantasy Game API示例中,您可以创建一个Fighter对象来表示游戏中的一种角色:

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
}

在这个例子中,Fighter对象类型已经被声明,并且它有四个命名字段:

  • id产生一个非空ID类型。
  • name产生一个非空String类型。
  • level产生一个Int类型。
  • active产生一个非空Boolean类型。

在声明之上,您还可以使用双引号添加注释,就像这个例子中的"具有直接战斗能力和力量的英雄"。这将显示为类型的描述。

在这个例子中,每个字段都解析为标量类型,但对象字段也可以解析为其他对象类型。例如,您可以创建一个Weapon类型,并且GraphQL模式可以被设置为Fighter上的weapon字段将解析为一个Weapon对象:

"A valiant weapon wielded by a fighter."
type Weapon {
  name: String!
  attack: Int
  range: Int
}

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
  weapon: Weapon
}

对象也可以嵌套到其他对象的字段中。

根操作类型

有三个特殊的对象,它们充当GraphQL模式的入口点:Query(查询)、Mutation(变更)和Subscription(订阅)。这些被称为根操作类型,并遵循与任何其他对象类型相同的规则。

关键字schema表示进入GraphQL模式的入口点。您的根Query、Mutation和Subscription类型将位于根schema对象上:

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

Query类型在任何GraphQL模式上都是必需的,表示读取请求,类似于REST API的GET。以下是一个根Query对象的示例,返回Fighter类型的列表:

type Query {
  fighters: [Fighter]
}

Mutations表示写入请求,类似于REST API中的POSTPUTDELETE。在以下示例中,Mutation具有一个带有命名参数inputaddFighter字段:

type Mutation {
  addFighter(input: FighterInput): Fighter
}

最后,订阅对应于事件流,可与Web应用程序中的Websocket结合使用。在GraphQL Fantasy API中,它可能用于随机战斗遭遇,如下所示:

type Subscription {
  randomBattle(enemy: Enemy): BattleResult
}

请注意,schema入口点在某些GraphQL实现中通常被抽象化。

字段参数

GraphQL对象的字段本质上是返回值的函数,并且它们可以接受任何函数一样的参数。字段参数由参数名称后跟类型定义。参数可以是任何非对象类型。在此示例中,Fighter对象可以通过id字段进行过滤(该字段解析为非空ID类型):

type Query {
  fighter(id: ID!): Fighter
}

此特定示例适用于从数据存储中获取单个项目,但参数也可用于过滤、分页和其他更具体的查询。

接口类型

与对象类型类似,抽象接口类型由命名字段及其关联值类型列表组成。接口看起来和遵循对象的所有相同规则,但用于定义对象实现的子集。

到目前为止,在你的模式中,你有一个Fighter对象,但你可能还想要创建一个Wizard,一个Healer,以及其他一些对象,它们将共享许多相同的字段,但也有一些差异。在这种情况下,你可以使用接口来定义它们共有的字段,并创建实现了该接口的对象。

在下面的示例中,你可以使用interface关键字创建一个BaseCharacter接口,其中包含每种类型角色都将拥有的所有字段:

"A hero on a quest."
interface BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job
}

每种角色类型都将拥有idnamelevelspeciesjob字段。

现在,想象一下你有一个Fighter类型和一个Wizard类型,它们拥有这些共享字段,但Fighters使用Weapon,而Wizards使用Spells。你可以使用implements关键字来将每个类型标记为BaseCharacter的实现,这意味着它们必须拥有来自创建的接口的所有字段:

"A hero with direct combat ability and strength."
type Fighter implements BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job!
  weapon: Weapon
}

"A hero with a variety of magical powers."
type Wizard implements BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job!
  spells: [Spell]
}

FighterWizard都是BaseCharacter接口的有效实现,因为它们具有所需的字段子集。

Union Type

另一种可以与对象一起使用的抽象类型是\texttt{Union}类型。使用\texttt{union}关键字,您可以定义一个包含一系列对象的类型,这些对象都可以作为响应。

使用前一节中创建的接口,您可以创建一个\texttt{Character}联合体,将字符定义为\texttt{Wizard}或\texttt{Fighter}:

union Character = Wizard | Fighter

等号字符(\texttt{=})设置了定义,而竖线字符(\texttt{|})则作为\texttt{OR}语句的功能。请注意,联合体必须由对象或接口组成。标量类型在联合体中无效。

现在,如果您查询字符列表,它可以使用\texttt{Character}联合体,并返回所有\texttt{Wizard}和\texttt{Fighter}类型。

结论

在这个教程中,你学习了许多定义了 GraphQL 类型系统 的类型。最基本的类型是标量类型,它们是作为模式树上叶子的值,包括 IntFloatStringBooleanID,以及 GraphQL 实现决定创建的任何自定义标量。枚举是有效常量值的列表,当你需要比简单声明为 String 更多控制响应时可以使用它们,它们也是模式树上的叶子。列表和非空类型被称为类型修饰符,或包装类型,它们可以分别定义其他类型为集合或必需。对象是模式树的分支,几乎在 GraphQL 模式中的一切都是对象类型,包括 querymutationsubscription 入口点。接口和联合类型是抽象类型,对于定义对象非常有帮助。

要进一步学习,你可以通过阅读 如何在 Node.js 中设置 GraphQL API 服务器 教程来练习创建和修改 GraphQL 模式,以便拥有一个可用的 GraphQL 服务器环境。

Source:
https://www.digitalocean.com/community/conceptual-articles/understanding-the-graphql-type-system