GraphQL学习笔记(4)Schema 和类型

1、类型系统Type System

 GraphQL 查询语言基本上就是关于选择对象上的字段。

查询示例:

{
  hero {
    name
    appearsIn
  }
}

  我们以一个特殊的对象 "root" 开始

  1. 选择其上的 hero 字段
  2. 对于 hero 返回的对象,我们选择 name 和 appearsIn 字段

因为一个 GraphQL 查询的结构和结果非常相似,因此即便不知道服务器的情况,你也能预测查询会返回什么结果。但是一个关于我们所需要的数据的确切描述依然很有意义,我们能选择什么字段?服务器会返回哪种对象?这些对象下有哪些字段可用?这便是引入 schema 的原因。

每一个 GraphQL 服务都会定义一套类型,用以描述你可能从那个服务查询到的数据。每当查询到来,服务器就会根据 schema 验证并执行查询。

2、类型语言:

GraphQL 服务可以用任何语言编写,因为我们并不依赖于任何特定语言的句法句式(譬如 JavaScript)来与 GraphQL schema 沟通,我们定义了自己的简单语言,称之为 “GraphQL schema language” —— 它和 GraphQL 的查询语言很相似,让我们能够和 GraphQL schema 之间可以无语言差异地沟通。

3、对象类型和字段

一个 GraphQL schema 中的最基本的组件是对象类型,它就表示你可以从服务上获取到什么类型的对象,以及这个对象有什么字段。使用 GraphQL schema language,我们可以这样表示它:

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

  Character 是一个 GraphQL 对象类型,表示其是一个拥有一些字段的类型。你的 schema 中的大多数类型都会是对象类型。

  • name 和 appearsIn 是 Character 类型上的字段。这意味着在一个操作 Character 类型的 GraphQL 查询中的任何部分,都只能出现 name 和 appearsIn 字段。
  • String 是内置的标量类型之一 —— 标量类型是解析到单个标量对象的类型,无法在查询中对它进行次级选择。
  • String! 表示这个字段是非空的,GraphQL 服务保证当你查询这个字段后总会给你返回一个值。在类型语言里面,我们用一个感叹号来表示这个特性
  • [Episode]! 表示一个 Episode 数组。因为它也是非空的,所以当你查询 appearsIn字段的时候,你也总能得到一个数组。数组内的元素可以是空。

4、参数

GraphQL 对象类型上的每一个字段都可能有零个或者多个参数,例如下面的 length 字段: 

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

  所有的参数必须是具名的,本例中为unit,类型为LengthUnit,对于可选的参数我们可以定义一个默认值,本例中的默认值为METER,因为unit后面没有!

5、查询和变更类型。

你的 schema 中大部分的类型都是普通对象类型,但是一个 schema 内有两个特殊类型:

schema {
  query: Query
  mutation: Mutation
}

  每一个 GraphQL 服务都有一个 query 类型,可能有一个 mutation 类型。这两个类型和常规对象类型无差,但是它们之所以特殊,是因为它们定义了每一个 GraphQL 查询的入口

这样一个查询:

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

  那表示这个 GraphQL 服务需要一个 Query 类型,且其上有 hero 和 droid 字段:

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

  Mutation类型同Query类型基本一致,都是定义了入口hero,droid

6、标量类型Scalar Types

标量类型没有任何次级字段了,他们就是最末级

GraphQL 自带一组默认标量类型:

  • Int:有符号 32 位整数。
  • Float:有符号双精度浮点值。
  • String:UTF‐8 字符序列。
  • Booleantrue 或者 false
  • ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。

大部分的 GraphQL 服务实现中,都有自定义标量类型的方式。例如,我们可以定义一个 Date 类型:

scalar Date

  然后就取决于我们的实现中如何定义将其序列化、反序列化和验证。例如,你可以指定 Date 类型应该总是被序列化成整型时间戳,而客户端应该知道去要求任何 date 字段都是这个格式。

7、枚举类型

枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。

  1. 验证这个类型的任何参数是可选值的的某一个
  2. 与类型系统沟通,一个字段总是一个有限值集合的其中一个值

用 GraphQL schema 语言表示的 enum 定义:

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

  这表示无论我们在 schema 的哪处使用了 Episode,都可以肯定它返回的是 NEWHOPEEMPIRE 和 JEDI 之一


8、列表和非空
对象类型、标量以及枚举是 GraphQL 中你唯一可以定义的类型种类。
使用!标示非空。示例:
type Character {
  name: String!
  appearsIn: [Episode]!
}

  此处我们使用了一个 String 类型,并通过在类型名后面添加一个感叹号!将其标注为非空。这表示我们的服务器对于这个字段,总是会返回一个非空值。

非空类型修饰符也可以用于定义字段上的参数,如果这个参数上传递了一个空值。

示例:

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

  列表的运作方式也类似:我们也可以使用一个类型修饰符来标记一个类型为 List,表示这个字段会返回这个类型的数组。在 GraphQL schema 语言中,我们通过将类型包在方括号([ 和 ])中的方式来标记列表。

非空可以和列表组合使用。

9、接口

一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口

定义接口:

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
}

  注意下面例子的查询会产生错误:

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

  hero 字段返回 Character 类型,取决于 episode 参数,它可能是 Human 或者 Droid 类型。上面的查询中,你只能查询 Character 接口中存在的字段,而其中并不包含 primaryFunction

可以使用内联片段返回特定的查询。

10、联合类型

联合类型和接口十分相似,但是它并不指定类型之间的任何共同字段。

示例:

union 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",
        "height": 1.5
      },
      {
        "name": "TIE Advanced x1",
        "length": 9.2
      }
    ]
  }
}

  11、输入类型

输入对象看上去和常规对象一模一样,除了关键字是 input 而不是 type

input ReviewInput {
  stars: Int!
  commentary: String
}

  你可以像这样在变更(mutation)中使用输入对象类型:

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

  可以使用输入复杂的对象了:review并不是标量或者枚举类型,而是复杂的对象,这是由于我们定义了输入类型的缘故。

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

  结果:

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

  输入对象类型的字段当然也不能拥有参数。




posted @ 2018-08-25 12:11  tutu_python  阅读(926)  评论(0)    收藏  举报