[译] GraphQL - 学习 - 查询和改动

字段 / Fields

在最简单的情况下,GraphQL 是关于请求对象中的指定字段。让我们从一个非常简单的查询开始,以及我们运行它时得到的结果:

参数

{
  hero {
    name
  }
}

结果

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

你可以看到查询请求的形状与结果完全相同。这对于 GraphQL 至关重要,因为总是返回你所期望的内容,而服务器知道客户端要求的是什么字段

字段 name 返回一个 String 类型,在本例中是星战的主英雄的名字,"R2-D2"

MORE: 上面的查询是 交互式的 。这意味着你可以按照你的喜好来更改它,并查看新的结果。尝试在查询中向 hero 对象添加一个 appearsIn 字段,并查看新结果.

在前面的例子中,我们只要求返回一个 String 类型的英雄的名字,但是字段也可以引用到对象。在这种情况下,您可以为该对象进行子选择。GraphQL 查询可以遍历相关对象和它们的字段,让客户端在一个请求中获取大量相关数据,而不是像传统 REST 架构中需要的那样进行多次往返请求.

请求

{
  hero {
    name
    # Queries can have comments!
    friends {
      name
    }
  }
}

结果

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

请注意,在本例中,friends 字段返回数组条目。对于单个项目或项目列表,GraphQL 查询看起来都是一样的,但是我们知道根据语法中所显示的内容就知道需要哪些内容

参数 / Arguments

如果我们唯一能做的就是遍历对象和它们的字段,那么 GraphQL 对于数据获取来说是非常有用的语言。但是当你通过添加参数来获取到字段,事情会变得更有趣

参数

{
  human(id: "1000") {
    name
    height
  }
}

结果

{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 1.72
    }
  }
}

在像 REST 这样的系统中,你只能传递一组参数 -- 查询参数和URL部分. 但是在 GraphQL 中,每个字段和嵌套对象都可以获得自己的参数. 从而使 GraphQL 完全取代了多个 API 的获取。你甚至可以将参数传递给标量字段,以便在服务器上实现数据转换,而不是单独地对每个客户端进行数据转换

请求

{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}

结果

{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}

参数可以有很多不同的类型。在上面的例子中,我们使用了枚举类型,它代表了一组有限的选项(在本例中是长度单位,即 METERFOOT)。GraphQL 附带了一组默认的类型,但是 GraphQL 服务器还可以声明自己的自定义类型,只要它们可以序列化为你的传输格式

阅读更多关于 GraphQL 类型

关联 / Aliases

如果你有发现的眼睛,你可能已经注意到,由于结果对象字段与查询字段名称匹配,但不包含参数,你不能直接用不同的参数查询相同的字段。这就是为什么需要别名——它让你将字段的结果重命名为您想要的任何东西。

参数

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}

结果

{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

在上面的例子中,两个 hero 字段会发生冲突,但是由于我们可以将它们别名为不同的名称,所以我们可以在一个请求中得到两个结果

URL 片段 / Fragments

假设我们的应用程序中有一个相对复杂的页面,让我们查看两个英雄,以及他们的朋友。你可以想象这样的查询会很快变得复杂,因为我们需要重复字段至少两次 -- 用于比较英雄的每一个方面

请求参数

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

变量 / Variables

目前为止,我们已经在查询字符串中写入了所有的参数. 但在大多数应用程序中,对字段的参数将是动态的 : 例如,可能有一个下拉菜单,可以让你选择您感兴趣的星球大战,或者搜索字段,或者一组过滤器.

在查询字符串中直接传递这些动态参数不是一个好主意,因为我们的客户端代码需要在运行时动态地操作查询字符串,并将其序列化为特定于 GraphQL 的格式。相反,GraphQL 有一种推荐的方法,可以将动态值从查询中分离出来,并将它们作为单独的字典传递。这些值称为变量。

当我们开始处理变量时,我们需要做三件事:

  1. $variableName 替换查询中的静态值
  2. $variableName 声明为查询接受的变量之一
  3. 传递 variableName: value 在单独的、特定传输的(通常是JSON)的变量字典

例子
请求

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

变量

{
  "episode": "JEDI"
}

结果

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

现在,在我们的客户端代码中,我们可以简单地传递一个不同的变量,而不需要构造一个全新的查询。这也是一个很好的实践,用来表示我们的查询中哪些参数是动态的 -- 我们永远不应该做字符串插值来构造用户提供的值的查询

变量定义 / Variable definitions

在上面的查询中,变量定义是看起来像($episode: Episode)的部分。它的工作原理与在类型化语言中的函数的参数定义类似。它列出了所有的变量,前缀是 $,根据它们的类型,在本例中是 Episode

所有声明的变量必须是标量、枚举或输入对象类型。因此,如果您想将一个复合对象传递到一个字段中,你需要知道在服务器上对这些类型进行匹配. 在语法页面上了解更多关于输入对象类型的信息.

变量定义可以是可选的,也可以是必需的。在上面的例子中,因为没有 ! 在事件类型旁边,它是可选的。但是,如果将变量传入的字段需要非空参数,那么变量也必须是必需的。

要了解更多关于这些变量定义的语法,学习 GraphQL 语法是很有用的。语法在语法页面上有详细的解释.

默认变量 / Default variables

默认值也可以通过在类型声明之后添加默认值来分配给查询中的变量

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

当为所有变量提供默认值时,你可以调用查询而不传递任何变量。如果将任何变量作为变量字典的一部分传递,它将覆盖默认值

操作名称 / Operation name

我们在上面的例子中还看到,我们的查询已经获得了一个 operation name(操作名)。到目前为止,我们一直在使用一个简单的语法,在这里我们省略了 query 关键字和查询名称,但是在生产环境中,使用这些代码会让我们的代码少一些疑惑.

可以把它想象为你喜欢的编程语言中的函数名。例如,在JavaScript中,我们可以轻松地使用匿名函数,但是当我们给一个函数一个名字时,会更容易追踪它,调试我们的代码,并在它被调用时记录日志。同样地,GraphQL 查询和突变名,以及片段名,都可以成为服务器端用来识别不同的 GraphQL 请求的有用调试工具

指令 / Directives

我们在上面讨论了如何使用变量让我们避免使用手动字符串构造值来构造动态查询。在参数中传递变量解决了这些问题中一个相当大的问题,但是我们可能还需要一种方法来使用变量动态地更改查询的结构和形状。例如,我们可以想象一个具有概括和详细视图的UI组件,其中一个包含的字段比另一个要多。

让我们为这样一个组件构造一个查询:

查询规则

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}

变量

{
  "episode": "JEDI",
  "withFriends": false
}

结果

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

试着编辑上面的变量,然后将 withFriends 变更为 true,看看结果会如何变化

我们需要在 GraphQL 中使用一个名为 directive(指令) 的新特性。一个指令可以附加到一个字段或片段,并且可以以任何服务器需要的方式影响该查询的执行。核心 GraphQL 规范包含了两个指令,必须支持任何兼容规则的 GraphQL 服务器实现:

  • @include(if: Boolean) 只在结果中包含该字段,如果参数为 true
  • @skip(if: Boolean) 如果是 true, 则跳过这个字段

如果需要执行字符串操作来添加和删除查询中的字段,那么这些指令可能会非常有用。服务器实现还可以通过定义完全新的指令来添加实验特性。

变动,改动 / Mutations

大多数关于 GraphQL 的讨论都集中在数据获取上,但是任何完整的数据平台都需要一种方法来修改服务器端数据

在 REST 中,任何请求都可能在服务器上引发一些变动,但是根据约定,不建议使用 GET 请求来修改数据。GraphQL 是类似的 -- 从技术上讲,任何查询都可以实现数据写入。但是, 建立一个约定,任何导致写的操作都应该通过一个 Mutations 来显式地发送,这是很有用的。

就像在查询中一样,如果 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 字段如何返回新创建的 starscommentary 字段。这在对现有数据进行改动时尤其有用,例如,当增加一个字段时,因为我们可以用一个请求对字段的新值进行变更和查询。

你可能还会注意到,在本例中,我们传入的 review 变量不是标量。它是一个输入对象类型,一种特殊类型的对象类型,可以作为参数传入。了解更多关于语法页面上的输入类型.

多字段变更 / Multiple fields in mutations

一个变更可以包含多个字段,就像查询一样。查询和变更之间有一个重要的区别,除了名字

当查询字段并行执行时,更该字段序列化运行, 一个接一个的.

这意味着,如果我们在一个请求中发送两个incrementCredits 变更,那么第一个将确保在第二个开始之前完成,确保我们不会以自己的竞争条件结束。

内部片段 / Inline Fragments

与许多其他类型系统一样,GraphQL 语法包括定义接口和复合类型的能力。在语法指南中了解它们。

如果你正在查询返回接口或复合类型的字段,则需要使用内联片段来访问底层具体类型的数据。最简单的例子是:

参数

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

变量

{
  "ep": "JEDI"
}

结果

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

在这个查询中,hero 字段返回 Character 类型,根据 episode 参数,它可能是一个 Human,也可能是一个 Droid。在直接选择中,你只能请求在 Character 接口上存在的字段,例如 name

要在具体类型上请求一个字段,你需要使用一个带有类型条件的内联片段。因为第一个片段被标记为 ... on Droid ,如果从 hero返回的 CharacterDroid 类型,那么只能执行primaryFunction 部分. 类似的是 Human 类型返回 height 高度

命名片段也可以以同样的方式使用,因为一个命名的片段总是带有一个类型

元字段 / Meta fields

在某些情况下,你不知道将从 GraphQL 服务中获得什么类型,您需要一些方法来确定如何在客户端处理这些数据。GraphQL 允许你在查询的任意点请求 __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"
      }
    ]
  }
}

在上面的查询中,search 返回一个可以是 三选一 的联合类型。如果没有类型的字段,就不可能区分不同类型的客户端。

GraphQL 服务提供了几个元字段,其余部分用于公开 Introspection 系统。

阅读 2.1k

推荐阅读
酸柠檬
用户专栏

柠檬是苦的. 酸柠檬更不知道是什么味道这就是程序的味道个中滋味, 无法对外人道深思回味, 终得豁然

18 人关注
44 篇文章
专栏主页