Schemas and Types

在此页面中,您将了解关于在类型系统的所有需要了解的内容,以及它如何描述可以查询哪些数据。由于GraphQL可以与任何后端框架或编程语言一起使用,我们将远离具体的具体细节,只讨论概念。

类型系统

如果您以前看到过一种查询,您知道在查询语言基本上是关于选择对象上的字段。例如,在下面的查询中:

{
  hero {
    name
    appearsIn
  }
}

结果

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ]
    }
  }
}
  1. 我们先从 "root" 对象开始
  2. 我们选择那个hero的字段
  3. 对于从hero返回字段,我们选择name和appearsIn字段

由于一种查询的形状与结果非常匹配,所以您可以在不知道有关服务器的情况的情况下预测查询返回的内容。但是,有一个我们可以要求的数据的精确描述是有用的-我们可以选择哪些字段?他们会返回什么样的物体?在这些子对象上有哪些字段可用?这就是模式的用武之地。

每个GraphQL服务定义了一组类型,它们完全描述了您可以在该服务上查询的可能数据集。然后,当查询进入时,它们将被验证并针对该模式执行。

类型语言 

GraphQL服务可以用任何语言编写。由于我们不能依赖特定的编程语言语法(比如JavaScript)来讨论GraphQL模式,我们将定义自己的简单语言。我们将使用“GraphQL模式语言”-它类似于查询语言,允许我们以一种GraphQL的语言来讨论基于语言的模式。

对象类型和字段

Graphql模式最基本的组件是对象类型,它们只代表您可以从服务中获取的对象类型,以及它拥有的字段。在GraphQL 模式语言中,我们可以这样表示:

type Character {
  name: String!
  appearsIn: [Episode]!
}

这门语言很容易读,但是让我们来复习一下,这样我们就可以有一个共同的词汇:

Character 是一个GraphQL对象类型,它包含了一些字段。模式中的大多数类型都是对象类型。

nameappearsIn 字段都属于Character 类型。这意味着name 和appearsIn 是唯一可以出现在运行于Character 的GraphQL查询中的任何部分的字段。

String 是内置标量类型之一-它们是解析为单个scalar types的类型,并且在查询中不能有子选择。稍后我们将更多地讨论scalar types 。

l  String! 表示字段是不可空的,这意味着在查询此字段时,GraphQL服务承诺始终给出一个值。在类型语言中,我们将用感叹号表示那些。

l  [Episode]! 表示一个Episode对象集合的数组。由于它也是不可以为空的,所以当您查询在字段时,您总是可以得到一个数组(包含零个或多个项)。

现在您知道了一种对象类型是什么样子的,以及如何读取在类型语言的基本内容。

参数

GraphQL对象中的每一个字段都可以有一个或者多个参数,以length字段为例:

type Starship {
  id: ID!
  name: String!
  length (unit: LengthUnit = METER): Float
}

所有参数都被命名,与JavaScript和Python这样的语言不同的是,函数中的所有参数都是按名称传递的。在这个例子中,length字段定义了一个参数unit。

参数可以是必需的,也可以是可选的。当参数是可选的时,我们可以定义一个默认值-如果单元参数不传递,它将默认设置为METER。

查询Mutation 类型

模式中的大多数类型都只是普通对象类型,但在模式中有两种特殊类型:

schema {
  query: Query
  mutation: Mutation
}

每个GraphQL服务都有一个query 类型,并且可能有或可能没有mutation 类型。这些类型与常规对象类型相同,但它们是特殊的,因为它们定义了每个GraphQL查询的入口点。因此,如果您看到一个查询,它看起来像:

query {
  hero {
    name
  }
  droid (id: "2000") {
    name
  }
}

结果

{
  "data": {
    "hero": {
      "name": "R2-D2"
    },
    "droid": {
      "name": "C-3PO"
    }
  }
}

这意味着在服务需要具有hero和droid字段的查询类型:

type Query {
  hero (episode: Episode): Character
  droid (id: ID!): Droid
}

Mutations 的工作方式类似-在Mutation 类型上定义字段,这些字段可以作为根mutation 字段,您可以在查询中调用这些字段。

重要的是要记住,除了作为模式的“entry point" 的特殊状态之外,Query 和Mutation 类型与任何其他GraphQL对象类型一样,它们的字段的工作方式完全相同。

标量类型

一种对象类型有一个名称和字段,但是在某个时候这些字段必须解析为一些具体的数据。这就是标量类型的来源:它们代表查询的叶子。

在下面的查询中,name和appearsIn将解析为标量类型: 

{
  hero {
    name
    appearsIn
  }
}

结果

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ]
    }
  }
}

我们知道这一点,因为这些字段没有子字段-它们是查询的叶子。

GraphQL附带了一组默认标量类型,如下:

 

l  int:一个有符号的32位整数。

l  浮点:一个有符号的双精度浮点值 。

l  字符串:一个UTF字符序列。

l  布尔:true或false。

l  id:id标量类型表示唯一标识符,通常用于对对象进行重新定位或作为缓存的键。id类型以与字符串相同的方式序列化。

 

在大多数GraphQL服务实现中,还有一种指定自定义标量类型的方法。例如,我们可以定义日期类型:

scalar Date

然后由我们的实现来定义应该如何序列化该类型,反序列化并验证。例如,可以指定始终将日期类型序列化为整数时间戳,并且你的客户端知道期望要格式成日期字段。

枚举类型

枚举类型是一种特殊的标量,它被限制在一组特定的允许值。这使您能够:

  1. 验证此类型的任何参数都是允许的值之一。
  2. 通过类型系统通信,字段将始终是有限的值集合之一。

 

下面是一个定义在模式语言中的样子:

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

这意味着无论我们在我们的模式中使用类型Episode ,我们期望它完全是一个NEWHOPEEMPIRE,或者 JEDI。

 

列表和非-Null

 对象类型、标量和枚举是可以在GraphQL中定义的唯一类型。但是,当您在模式的其他部分使用类型时,或者在查询变量声明中,可以应用影响这些值验证的附加类型修饰符。

让我们看一个例子:

type Character {
  name: String!
  appearsIn: [Episode]!
}

在这里,我们使用一个字符串类型并将它标记为非空,添加一个感叹号,!类型名称之后。这意味着我们的服务器总是期望返回这个字段的非空值,如果它最终得到一个空值,实际上会触发一个GraphQL执行错误,那么让客户端知道出了问题。

在为字段定义参数时,也可以使用非空类型修饰符,这将导致在服务器返回一个验证错误,如果一个空值作为该参数传递,无论是在字符串还是在变量中。

query DroidById($id: ID!) {
  droid(id: $id) {
    name
  }
}

参数

{
  "id": null
}

结果

{
  "errors": [
    {
      "message": "Variable \"$id\" of required type \"ID!\" was not provided.",
      "locations": [
        {
          "line": 1,
          "column": 17
        }
      ]
    }
  ]
}

列表的工作方式类似:我们可以使用类型修饰符将类型标记为列表,指示此字段将返回该类型的数组。在模式语言中,这是通过将类型包装在方括号中表示的,[和]。它对参数的作用是相同的,在这个参数中,验证步骤将期望这个值的数组。

可以将非空和列表修饰符组合在一起。例如,您可以有一个非空字符串列表:

myField: [String!]

这意味着列表本身可以是null,但它不能有任何空成员。例如,在JSON:

myField: null // valid
myField: [] // valid
myField: ['a', 'b'] // valid
myField: ['a', null, 'b'] // error

现在,假设我们定义了一个非空的字符串列表:

myField: [String]!

这意味着列表本身不能为空,但可以包含空值:

myField: null // error
myField: [] // valid
myField: ['a', 'b'] // valid
myField: ['a', null, 'b'] // valid

您可以任意嵌套任意数量的非空和列表修饰符,根据您的需要。

接口

与许多类型系统一样,GraphQL支持接口。接口是一个抽象类型,它包含一个特定的字段集合,一个类型必须包含的实现接口的字段。

 

例如,你可以有一个接口Character,在星球大战三部曲代表任何一个角色:

interface Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
}

这意味着任何实现Character的类型都需要这些精确的字段,其中包含这些参数和返回类型。

例如,下面是一些可能实现字符的类型:

type Human implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  starships: [Starship]
  totalCredits: Int
}

type Droid implements Character {
  id: ID!
  name: String!
  friends: [Character]
  appearsIn: [Episode]!
  primaryFunction: String
}

您可以看到,这两个类型都具有Character接口中的所有字段,totalCreditsstarships 和 primaryFunction 这是区别于接口Character中的字段。

 

当您想要返回一个对象或一组对象时,接口是有用的,但是这些对象可能有几种不同的类型。

 

例如,注意下面的查询会产生一个错误:

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    primaryFunction
  }
}

参数

{
  "ep": "JEDI"
}

结果

{
  "errors": [
    {
      "message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ]
    }
  ]
}

hero 字段返回类型Character,这意味着它可能是一个Human 或一个Droid ,取决于episode 的参数。在上面的查询中,您只能请求在Character接口上存在的字段,不包括primaryFunction。

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}

参数

{
  "ep": "JEDI"
}

结果

{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

在查询指南的行内片段部分中了解有关此内容的更多信息。

 

联合类型

联和类型与接口非常相似,但它们不能指定类型之间的任何公共字段。

union SearchResult = Human | Droid | Starship

返回一个SearchResult类型,我们都可以得到一个Human,一个Droid,或者一个Starship。联合类型的成员需要是具体的对象类型;您不能从接口或其他联合创建一个联合类型。

 

在这种情况下,如果查询返回在SearchResult 联合类型的字段,则需要使用条件片段才能查询任何字段:

{
  search(text: "an") {
    ... on Human {
      name
      height
    }
    ... on Droid {
      name
      primaryFunction
    }
    ... on Starship {
      name
      length
    }
  }
}

结果

{
  "data": {
    "search": [
      {
        "name": "Han Solo",
        "height": 1.8
      },
      {
        "name": "Leia Organa",
        "primaryFunction": "Astromech"
      },
      {
        "name": "TIE Advanced x1",
        "length": 9.2
      }
    ]
  }
}

输入类型

到目前为止,我们只讨论了将标量值(如枚举或字符串)作为参数传递到字段中。但是,您也可以轻松地传递复杂的对象。在发生mutations的情况下,这一点特别有用,您可能希望传入要创建的整个对象。

在 GraphQL schema 语言中。输入类型与常规对象类型完全相同,但使用input 而不是type:

input ReviewInput {
  stars: Int!
  commentary: String
}

下面是如何在一种修改中使用input对象类型:

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

参数

{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}

结果

{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

输入对象类型上的字段可以自己引用输入对象类型,但不能在模式中混合输入和输出类型。输入对象类型也不能在其字段上有参数。

posted @ 2017-11-11 00:43  黄海涛-前端开发  阅读(307)  评论(0)    收藏  举报