对GraphQL类型系统的理解

开场白

GraphQL 是一种用于促进前端和数据源之间通信的现代解决方案。所有关于GraphQL实现的细节和功能都在GraphQL Schema中定义。为了编写一个功能完整的GraphQL模式,您必须了解GraphQL类型系统。

在这篇文章中,您将了解有关GraphQL类型的内容:五种内置标量类型、枚举类型、列表和非空类型、对象类型以及与它们一起使用的抽象接口和联合类型。您将会通过每种类型的示例进行复习,并学习如何使用它们来构建一个完整的GraphQL模式。

先决条件

为了从这个教程中获益最大化,你应该具备以下条件:

  • An understanding of the fundamental concepts of GraphQL, which are laid out in An Introduction to GraphQL.
  • A GraphQL environment, an example of which can be found in How to Set Up a GraphQL API Server in Node.js.

标量类型

一个GraphQL schema中的所有数据最终都会解析为不同的标量类型,这些类型代表原始值。GraphQL的响应可以被表示为一棵树,而标量类型则是树末端的叶子节点。在一个嵌套的响应中可以有多个层级,但是最后一层总是会解析为标量(或者Enum)类型。GraphQL内置了五种标量类型:Int、Float、String、Boolean和ID。

我正在吃晚饭。

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

浮动

浮点数是一种有符号的双精度小数值。它是一个带有小数点的有符号(正或负)数字,例如1.2。这是另一种用于数值数据的内置标量。

字符串 (zì fú

字符串是一个UTF-8字符序列。字符串类型用于任何文本数据。这也可以包括非常大的数字等数据。大多数自定义标量将是字符串数据类型。

布尔值 (bù’ěr zhí)

布尔值是一个真或假的值。

身份证

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

自定义标量

除了这些内置的标量之外,标量关键字还可以用于定义自定义标量。您可以使用自定义标量来创建具有附加服务器级验证的类型,例如日期、时间或URL。以下是定义新日期类型的示例:

scalar Date

服务器将知道如何使用GraphQLScalarType来处理与这种新类型的交互。

列举类型

枚举类型,也被称为”枚举器”类型,描述了一组可能的值。

在《如何使用GraphQL管理数据》系列的其他教程中使用了Fantasy Game API主题,你可以为游戏角色的职业和物种创建一个包含系统允许的所有值的枚举。枚举类型使用enum关键字进行定义,如下所示:

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

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

用这种方式可以保证一个角色的职业是“战士”或者“法师”,而不会出现“紫色”或其他随机字符串的情况,如果使用字符串类型而不是自定义枚举,这种情况可能发生。按照惯例,枚举类型的名称应该全部大写。

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

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

手的枚举类型已经声明为单手(SINGLE)和双手(DOUBLE)两个值,并且weapons字段的参数默认值为单手(SINGLE),意味着如果没有传入参数,则会回退到单手(SINGLE)。

非空类型

你可能注意到,在内置的标量类型列表中,空或未定义的null 是常见的类型,许多编程语言都认为它是一种原始的数据类型。然而,在GraphQL的内置标量类型列表中,并没有包含null。在GraphQL中,null代表缺少具体数值的情况。

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

列表类型

在GraphQL中,List类型是另一种类型修饰符。任何用方括号([])包裹的类型都会变成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 yields a Non-Null ID type.
  • name yields a Non-Null String type.
  • level yields an Int type.
  • active yields a Non-Null Boolean type.

在声明之上,您还可以使用双引号添加评论,例如:”具有直接战斗能力和力量的英雄。” 这将作为类型的描述显示出来。

在这个例子中,每个字段都解析为一个标量类型,但是对象字段也可以解析为其他对象类型。例如,您可以创建一个武器类型,GraphQL模式可以被设置为战士的武器字段解析为一个武器对象。

"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 {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

查询类型在任何GraphQL架构中都是必需的,表示一个读取请求,类似于REST API的GET请求。以下是一个返回Fighter类型列表的根查询对象的示例。

type Query {
  fighters: [Fighter]
}

突变代表了一个写入请求,类似于REST API中的POST、PUT或DELETE操作。在下面的示例中,Mutation具有一个addFighter字段,带有一个命名参数(input)。

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

最后,订阅对应于事件流,在Web应用中与Websocket一起使用。在GraphQL Fantasy API中,它或许可以用于随机战斗遭遇,如下:

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

请注意,在一些GraphQL实现中,模式入口点常常被封装起来。

领域争论

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

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

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

接口类型

与对象类型相似,抽象的接口类型由一系列具有名称的字段和它们关联的值类型组成。接口的外观和规则与对象相同,但用于定义对象实现的一个子集。

到目前为止,在你的架构中,你有一个战士对象,但你可能还想创建一个法师、一个治疗师和其他一些对象,它们将共享许多相同的字段,但也有一些不同之处。在这种情况下,你可以使用接口来定义它们共有的字段,并创建实现该接口的对象。

在以下示例中,您可以使用接口关键字创建一个BaseCharacter接口,其中包含每种角色都具备的所有字段。

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

每个角色类型都会拥有id、姓名、等级、物种和职业等字段。

现在,想象一下你有一个战士类型和一个法师类型,它们具有相同的字段,但是战士使用武器,而法师使用法术。你可以使用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]
}

战士和法师都是BaseCharacter接口的有效实现,因为它们具备所需的字段子集。

联合类型

还有另一种可以与对象一起使用的抽象类型,那就是联合类型。使用union关键字,您可以定义一个包含多个对象的类型,这些对象都可以作为有效的响应。

在之前的部分创建的接口中,你可以创建一个角色联合体,将角色定义为巫师或战士。

union Character = Wizard | Fighter

等号(=)表示定义,而竖线(|)表示或语句。请注意,Union必须由对象或接口组成,标量类型在Union中无效。

现在如果您查询字符列表,它可以使用字符联合,返回所有巫师和战士类型。

结论

在本教程中,你了解了GraphQL类型系统中定义的许多类型。最基础的类型是标量类型,它们是模式树上的叶子节点(对应的值),包括Int、Float、String、Boolean、ID以及GraphQL实现决定创建的任何自定义标量。枚举是有效常量值的列表,当你需要比简单声明字符串更精确地控制响应时可以使用,并且也是模式树上的叶子节点。列表和非空类型被称为类型修饰符或封装类型,它们可以定义其他类型作为集合或必须的类型。对象是模式树的分支,几乎在GraphQL模式中的所有东西都是对象类型,包括查询、变更和订阅入口点。接口和联合类型是可以帮助定义对象的抽象类型。

为了进一步学习,你可以通过阅读《如何在Node.js设置GraphQL API服务器》的教程来练习创建和修改GraphQL模式,以获得一个可工作的GraphQL服务器环境。

发表回复 0

Your email address will not be published. Required fields are marked *


广告
将在 10 秒后关闭
bannerAds