GraphQL是什么?

因为我只写了查询和变更,所以其他内容请参考文献
(会花费时间,所以我会逐渐添加)
(可以先草稿,但可能会失去动力)

首先

这篇文章旨在尽可能简洁地叙述。如果您想了解更多详细信息,请查阅其他资料。

在这篇文章中,我们对数据进行了一些整理,以使其更易于阅读。

翻译:GraphQL 是什么

GraphQL是一种用于API的语言。它只定义了数据的格式,因此不依赖于语言或数据存储的方式(可以是数据库或文本)。
按照GraphQL的定义编写查询,并通过与服务器通信返回JSON。

查询和变更

GraphQL提供了各种关于数据获取和编辑的功能。

领域

最简单的是这个领域。

{
  hero {
    name
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

以相同形式的JSON返回所请求的查询。这对GraphQL来说很重要。(即可立即获取所需数据)

除了接受单一的String、int等类型,还可以接受Object类型作为参数。此外,注释以#开头表示。

{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

在上面的例子中,”friends” 将返回一个数组对象。

争论

只有通过上面的Field可以接收指定的数据。
因此,通过添加Arguments可以灵活地指定和获取各种数据。

{
  human(id: "1000") {
    name
    height
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

过去常见的API设计中,REST只能通过URL查询等方式发送参数。然而,在GraphQL中,可以为每个字段等添加参数。

{
  human(id: "1000") {
    name
    height(unit: cm)
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 172
    }
  }
}

该参数可以有各种不同的类型。您也可以自行声明。(请参考后面的模式和类型有关的信息)

别名

如果在同一查询中尝试引用hero(episode:EMPIRE)和hero(episode:JEDI),它们将成为相同的hero字段,无法引用。因此我们可以使用别名来解决这个问题。

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

通过使用别名,您可以同时接收两个结果。

片段

重复声明相同数据是冗余的,只需声明一次即可。

{
  leftComparison: hero(episode: EMPIRE) {
    name
    appearsIn
    friends {
      name
    }
  }
  rightComparison: hero(episode: JEDI) {
    name
    appearsIn
    friends {
      name
    }
  }
}

通过使用碎片,可以简单地声明上述示例如下。

{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ],
      "friends": [
        { "name": "Han Solo" },
        { "name": "Leia Organa" },
        { "name": "C-3PO" },
        { "name": "R2-D2" }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ],
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

在获取相同格式的大量数据时非常方便易用。也可以使用多个片段。

变量

在大多数的应用程序中,需要在参数中指定的内容是会变化的。
在GraphQL中,不建议直接写变化的参数,因为GraphQL会在内部将其转换为特定的格式。
因此,建议使用变量。

首先,我们会像$变量名那样声明使用变量的查询语句。(第一行)
然后,我们会像过去一样书写使用该变量的查询语句。(第二行)

query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

将变量名称描述为类似值的形式,并创建变量字典(通常为Json)。

{
  "episode": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

在客户端,不需要创建新查询,只需要传递变量(Json)即可。
不应该使用变量来通过字符串拼接创建查询。

关于变量定义

变量定义类似于函数声明,变量定义以($episode: Episode)的方式声明,$作为变量名的前缀,后面是声明的类型。

在上述的例子中,$episode并非必需项。
要使其成为必需项,请声明为($episode: Episode!)。
(更详细的信息,请参考后文的模式和类型)

也可以声明默认值。
可以这样声明: ($episode: Episode = JEDI)。
当然,如果给定了变量(Json),将优先使用Json。

操作名称

在上面的例子中,我们写了query HeroNameAndFriends { … },但是按照以前的方式,我们也可以省略它们,并简单地写成{ … }。

通过编写这些代码,可以简化服务器和客户端之间的请求识别过程。

指示

通过使用变量,您可以更改查询的参数。但是,可能还需要根据变量来更改获取的数据,而不仅仅是参数。

query Hero($episode: Episode, $withAppearsIn: Boolean!, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    appearsIn @skip(if: $withAppearsIn)
    friends @include(if: $withFriends) {
      name
    }
  }
}
{
  "episode": "JEDI",
  "withAppearsIn": true,
  "withFriends": true
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        { "name": "Luke Skywalker" },
        { "name": "Han Solo" },
        { "name": "Leia Organa" }
      ]
    }
  }
}

通过上面的例子,我们可以看出$with—-变量的变化。

如果是true,则会显示@include。如果是false,则会显示@skip。

突变

这次不仅涉及数据获取,还涉及编辑和添加功能。虽然GraphQL更加注重数据获取,但服务器端的数据编辑也是非常重要的。

在REST中,不推荐使用GET来修改数据,在GraphQL中也不推荐使用查询来修改数据。
在REST中通常使用POST、PUT等方法,但在GraphQL中建议使用mutations。

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

针对所请求的数据,返回更新后的数据作为响应。当您注意到createReview函数的review参数不是单一值而是对象时,请参考后续的模式(schema)和类型(type)。

尽管查询会并行执行,但更改操作会按顺序执行。
这意味着在一个请求中执行多个更改操作时,保证第一个更改操作先于第二个操作完成,并确保数据不会冲突。

内联片段

在GraphQL中,我们还可以利用接口来表示类型。(更多详情,请参考后面的模式和类型)

根据类型,您可以更改获取的数据。

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}
{
  "ep": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

根据返回的Droid or Human(接口为Character)来展示与之相关的英雄。

特殊领域

通过将__typename包含在请求中,可以参考返回的类型。

{
  search(text: "an") {
    __typename
    ... on Human {
      name
    }
    ... on Droid {
      name
    }
    ... on Starship {
      name
    }
  }
}
{
  "data": {
    "search": [
      {
        "__typename": "Human",
        "name": "Han Solo"
      },
      {
        "__typename": "Human",
        "name": "Leia Organa"
      },
      {
        "__typename": "Starship",
        "name": "TIE Advanced x1"
      }
    ]
  }
}

请参考后续的自省之处以找到类似特殊物品的信息。

架构和类型

为了不依赖于特定的实现语言,GraphQL具有自己独特的类型概念。

对象类型和字段

GraphQL模式的基本组件是对象类型。
它表示从服务中获取的对象的类型和字段。
在模式中可以这样写。

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

CharacterはGraphQLのオブジェクト型です. 複数のフィールドを持ちます. GraphQLのスキーマはほとんどこのオブジェクト型です.

nameとappearsInはCharacter型のフィールドです. これらはQueryから参照することが出来ます.

Stringは実装されたスカラー型の一つです. 詳しくはあとで.

String!はフィールドがnullでないことを保証します. つまり, このフィールドを参照すると常に値が返ってきます.

[Episode!]はEpisodeオブジェクトの配列を表します.またnullで無いので, 常に配列を返します.(配列の大きさが0を含む)

争论

在GraphQL的字段中可以添加参数。

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

在上述的例子中,我们定义了一个名为length的字段。
参数并不是必需的,可以设置默认值,在上述的例子中,默认值被设定为METER。

查询类型和变异类型

通常情况下,模式是对象类型的,但有两种特殊的模式。

schema {
  query: Query
  mutation: Mutation
}

所有的GraphQL服务都会有查询类型,并且可能还会有变更类型。
这些都被定义为查询的入口点。

例如

query {
  hero {
    name
  }
  droid(id: "2000") {
    name
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    },
    "droid": {
      "name": "C-3PO"
    }
  }
}

如果执行类似的查询,则必须定义以下字段的Query类型:hero和droid。

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

在Mutations中,也需要定义一个Mutation类型的字段。除了作为模式的入口点以外,它与对象类型相同,并且字段也会以相同的方式工作。

标量类型

在对象类型中,需要定义名称和字段,但必须在某个地方将这些字段解析成具体的数据。
这就是标量类型。GraphQL默认提供了以下一组标量类型。

スカラー型名内容Int符号付き32ビット整数Float符号付き倍精度浮動小数点値StringUTF-8文字シーケンスBooleantrueまたはfalseIDユニークな値が定義され, 再取得時等にデータを検証する

当然可以自己定义标量类型。

scalar Date

对于将该类型进行序列化、反序列化和验证的操作取决于内部实现。

枚举类型

列举类型定义了具有确定值的元素,但是不允许在其中声明重复的值。

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

上面的例子定义了一个Episode枚举类型,并声明了NEWHOPE、EMPIRE和JEDI这三个值。

在各种不同语言的GraphQL实现中,可以使用语言中存在的枚举(Enum),对于像JavaScript这样没有枚举的语言可能需要自己实现,但这与客户端无关,作为GraphQL,枚举完全可以正常运作。

列表和非空

对象类型、标量类型和枚举类型是可以在GraphQL中定义的类型。但在实际使用时,除了这些类型,还可以应用额外的类型修饰符。

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

在上述例子中,对String类型添加!,并将其设为非可空类型。

query searchWord($word: String!)
{
  "word": null
}

如果按照上述方式声明并执行,将输出以下验证错误。

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

列表也可以像这样工作。例如,如果使用[String],它将返回一个String类型的数组。
另外,!和[]也可以组合使用。

fieldName: [String!]

假设, 列表本身可以是null, 但不能存在null元素.
可以采取以下对应措施。

fieldの中身エラーの有無null無[]無[“a”, “b”]無[“a”, null, “b”]有

如果定义一个非null非可为空类型的列表

fieldName: [String]!
fieldの中身エラーの有無null有[]無[“a”, “b”]無[“a”, null, “b”]無

会变成像这样。

界面

与其他语言一样,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接口中定义的字段,还可以声明类型特有的字段,如totalCredits、starships和primaryFunction。

接口很方便,但请小心使用方法。

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

由于primaryFunction字段只在Droid类型中声明,所以会出现错误。

条件性片段

为了避免这个问题,您可以使用内联片段来仅获取Droid类型的内容。

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

请参考此处的内联片段。

联合类型

共用体和接口非常相似,但是不需要在每个共用体中定义相同的字段。

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

输入类型

在之前的描述中,我们讲解了如何将标量类型(如enum和string)作为参数传递给字段。在GraphQL中,我们不仅可以传递标量类型,还可以简单地传递对象类型(尤其经常在mutations中使用)。虽然对象类型看起来与type类型类似,但使用的关键字不同,要用input关键字来表示。

input ReviewInput {
  stars: Int!
  commentary: String
}

请按以下方式使用

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

可以在input字段中指定input类型,但输出不一定会以相同的形式输出。
另外,input类型不能在字段中带有参数。

验证

还在中途

执行

还没到达中间。

反省

还没有完成

文献引用

GraphQL介绍

广告
将在 10 秒后关闭
bannerAds