【翻译】 GraphQL (二)—— 查询与更新

在此节中,您将详细的了解如何查询GraphQL服务器。

字段 Fileds

最简单的,GraphQL是在对象上访问特定字段。首先,我们看一个简单的查询以及当查询后得到的结果:

查询 :

{    
  hero {
    name
  }
}

结果:

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

您可以看到,查询的语句和得到的结果架构师相同的。这是GraphQL一个重要特性,因为您总是能够获取到你想要的数据,并且服务器也能够知道客户端要求的字段。

该字段name返回一个String的类型,在这个例子中,这个 hero 字段的名字是星球大战中的英雄名称:R2-D2

还有一件重要的事 —— 上面的查询是动态的。这说明只要你喜欢,你可以修改并查看新的结果,尝试在查询中 appearsInhero对象添加字段,然后查看新的结果。

在前面的例子中,我们只查询返回 String 的英雄名称,但是字段也可以引用对象。在这种情况下,您可以为该对象进行子查询。GraphQL查询可以遍历相关对象及其字段,允许客户端在一次请求中获取大量的相关数据,而不是像REST架构中那样进行多次相关的查询。

查询:

{
  hero {
    name
    # 这是一句注释
    friends {
      name
    }
  }
}

结果:

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

请注意,在此例子中,friends字段返回一个数组,GraphQL查询对于单个对象和多个对象都一样,但是我们根据schema文件中定义好的内容知道哪个类型是我们需要的。

参数 Arguments

我们现在能做的是查询和遍历对象中的字段,GraphQL现在是一种非常有用的数据查询语言,但是当你给查询的语句添加参数时,一切将变得更加有趣:
查询:

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

结果:

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

在像REST这样的系统中,您只能传递一组参数——在你的请求中添加查询参数和URL。但是在GraphQL中,每个字段和嵌套的对象都可以获得自己的参数组,这就使得在REST中需要多个查询的接口在GraphQL中只需要单个。您甚至可以将参数传递到 scalar fields中,以便在服务器上实现一次的数据转换,而不是在每个客户端上分开执行。

查询

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

结果:

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

参数可以有许多不同的类型,在上面的实例中,我们使用了 Enumeration类型,它用来标识一组选项(在这种情况下,长度单位,METER或者FOOT)。GraphQL附带一组默认类型,但是GraphQL服务器也可以声明自己的自定义类型,只要它们可以为你传输格式序列化。

在此阅读更多有关GraphQL类型系统的信息

别名 Aliases

相信眼尖的你注意到了,由于结果对象字段与查询中的字段名称不匹配但不包含参数,因此不能直接查询具有不同参数的相同字段。这就是为什么需要别名的原因——它们允许您将字段的结果重命名为你喜欢的东西。

查询:

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

结果:

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

在上面的示例中,两个hero字段会发生冲突,单由于我们可以将它们设置别名为不同的名称,因此我们可以在一个请求中获取到两个结果。

片段 Fragments

假设我们的应用程序中有一个相对复杂的页面,让我们查看两位英雄以及他们的朋友,你可以想象到这个查询就会变得复杂,因为我们需要至少重复一次这些字段 —— 每一个朋友都有一个重复。

这就是为什么GraphQL包含片段 的可重用单元的原因。片段 允许您构建字段集,然后将它们包含在您需要的查询中。以下是如何使用片段解决上述情况的例子:

查询:

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

如果字段重复,您可以看到上述查询如何解决重复。片段 的概念经常用于将复杂的应用程序数据需求拆分为更小的块,尤其当您需要将大量具有不同片段UI组件组合到一个初始数据获取中时。

在片段内使用变量

片段 可以访问查询或者更新中声明的变量。

查询

query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

结果

{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "friendsConnection": {
        "totalCount": 4,
        "edges": [
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          },
          {
            "node": {
              "name": "C-3PO"
            }
          }
        ]
      }
    },
    "rightComparison": {
      "name": "R2-D2",
      "friendsConnection": {
        "totalCount": 3,
        "edges": [
          {
            "node": {
              "name": "Luke Skywalker"
            }
          },
          {
            "node": {
              "name": "Han Solo"
            }
          },
          {
            "node": {
              "name": "Leia Organa"
            }
          }
        ]
      }
    }
  }
}
操作名称

到目前为止,我们一直使用简写语法,我们省略了 query关键字和查询名称,但在正式环境中,使用它们使得我们代码更加清晰。

这里包含一个关键字的例子 query 作为操作类型和 HeroNameAndFriends作为操作名称:

查询:

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}

结果:

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

该操作类型可以是 querymutationsubscriptiondescribes,如果是不是使用query来获取数据,你必须声明操作类型,在这种情况下,您无法为操作提供名称或变量。

操作名称是一个有意义并且有明确的操作功能。它仅在多操作文档中需要,但是我们鼓励使用它,因为它对调试和服务器端日志记录非常有用。当您的网络日志或者GraphQL服务器出现问题时,通过名称在代码库中查询起来是很方便的,您可以将其视为您喜欢的编程语言中的函数名称。例如,在JavaScript中我们可以很容易地使用匿名函数,但是当我们给函数命名时,它更加容易跟踪、调试代码,并在调用时记录。同样,GraphQL查询和更新名称已经片段名称可以是服务器端有用的调试工具,用于识别不同的GraphQL请求。

变量 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"
        }
      ]
    }
  }
}

现在,在我们的客户端代码中,我们可以简单的传递一个不同的变量来查询,而不是像上面一样重新写一个新的查询。这是一个很好的写法,标识我们查询中的哪些参数应该是动态变化的 —— 我们应该永远不要使用字符串来构造一个查询。

变量定义

变量定义像是上面查询语句的 ($episode: Episode),它的工作方式与类型语言中函数的参数定义相同。前缀为:$, 后面跟上类型Episode

所有声明的变量必须是scalarsenumsinput object,因此,如果要将复杂的对象传递到字段中,则需要知道服务器上匹配的输入类型。在Schema Page上可以了解到更多的的对象类型信息。

变量定义可以是可空的或者是必须的。在上面的例子中,由于没有一个 !Episode类型旁,所以这是一个可选的,但是,如果你要传递变量的参数是非空的,那么该变量则是必须得。

要了解更多有关这些变量定义的语法的信息,所以了解GraphQL schema语言非常有用。在 Schema页面上详细解释了模式语言。

默认变量

通过在类型声明后添加默认值,还可以将默认值分配给查询中的变量。

查询:

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

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

指令 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,然后看看结果的变化。

我们需要在GraphQL中使用一个指令的新功能。指令可以附加到字段或片段中,并且可以以服务器期望的任何方式影响查询的执行。GraphQL规范包含两个指令,任何符合规范的GraphQL服务器都必须支持这两个指令:

  • @include(if : Boolean)如果仅在结果中包含此字段,则该参数为 true
  • @skip(if : Boolean) 如果需要跳过该字段,则参数为 true

指令可用于摆脱您需要进行字符串操作以在查询中添加和删除字段的情况。服务器实现还可以通过定义全新的指令来添加需要的功能。

更新 Mutations (突变、变化)

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

REST中,任何请求都可能对服务器有一些副作用,但是按照我们的习惯,建议不要使用GET请求来修改数据,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字段 返回我们插入的新数据包含starscommentary字段。在更新数据中尤为重要,例如,在递增字段时,因为我们可以使用一个请求来更新并查询更新的值。

您可能还会注意到,在此实例中,review变量不是一个scalar(简单的数据),它是一个可以作为参数传递的对象类型。在Schema页面上可以了解有关输入类型的更多信息。

在一个更新中更新多个字段

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

查询字段并行执行
更新字段串行执行

这意味着如果我们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类型的数据,该类型可以是Human或者是Droid,这取决于episode参数。在直接选中中,你只能询问Character上存在的字段,例如name.

要查询具体类型上的字段,需要使用带有类型条件的 内联片段 。因为第一个片段被标记为…on Droid,只有当hero返回的字符是Droid类型时,才会执行primaryFunction字段。类似地,对于human typeheight字段也是如此。

命名片段也可以以相同的方式使用,因为命名片段始终具有附加类型。

元字段 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返回一个联合类型,它可以是三个选项之一,如果没有该__typename,就不可能将客户的不同类型区分开来。

GraphQL服务提供了一些元字段,其余的用于公开Introspection系统。

上一篇 下一篇
GraphQL (一)—— 使用简介 待续
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349