GraphQL之基础篇 - 查询和变更
字段(Fields)
GraphQL请求对象上的特定字段,例:
// 请求
{
hero { name
friends { name
} }}
// 响应
{
"data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" } ] } }}
我们可以发现,查询和结果拥有一样的结构和字段。这是 GraphQL 最重要的特性,因为这样一来,你就总是能得到你想要的数据,而服务器也准确地知道客户端请求的字段。
参数(Arguments)
请求传参是必要的,也可以让查询变得更加灵活和方便,当然GraphQL也可以做到。
// 请求
{
human(id: "1000") {
name
height }}
// 响应
{
"data": { "human": { "name": "Luke Skywalker", "height": 1.72 } }}
别名(Aliases)
当需要重命名返回的字段名时,别名就有可以起到作用,如下例可以将hero替换为empireHero和jediHero。
// 请求
{
empireHero: hero(episode: EMPIRE) { name
} jediHero: hero(episode: JEDI) { name
}}
// 响应
{
"data": { "empireHero": { "name": "Luke Skywalker" }, "jediHero": { "name": "R2-D2" } }}
片段(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" } ] }, "rightComparison": { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ], "friends": [ { "name": "Leia Organa" } ] } }}
有了片段,可以大大简化我们的代码。片段的概念经常用于将复杂的应用数据需求分割成小块,特别是你要将大量不同片段的 UI 组件组合成一个初始数据获取的时候。
操作名称(Operation name)
这之前,我们都使用了简写句法,省略了 query 关键字和查询名称,但是生产中使用操作类型和操作名称可以使我们代码减少歧义。
下面的示例包含了作为操作类型的关键字 query 以及操作名称 HeroNameAndFriends:
// 请求
query HeroNameAndFriends { hero { name
friends { name
} }}
// 响应
{
"data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Leia Organa" } ] } }}
操作类型可以是 query、mutation 或 subscription,描述你打算做什么类型的操作。操作类型是必需的,除非你使用查询简写语法,在这种情况下,你无法为操作提供名称或变量定义。
操作名称是你的操作的有意义和明确的名称。
变量(Variables)
目前为止,我们将参数写在了查询字符串内。但是在很多应用中,字段的参数可能是动态的,如下例,可使我们的请求更加灵活。
// 定义变量
{
"episode": "JEDI"
}
// 请求
query HeroNameAndFriends($episode: Episode) { hero(episode: $episode) { name
friends { name
} }}
变量定义(Variable definitions)
变量定义看上去像是上述查询中的 ($episode: Episode)。其工作方式跟类型语言中函数的参数定义一样。它以列出所有变量,变量前缀必须为 $,后跟其类型,本例中为 Episode。
变量定义可以是可选的或者必要的。上例中,Episode 后并没有 !,因此其是可选的。但是如果你传递变量的字段要求非空参数,那变量一定是必要的。
默认变量(Default variables)
可以通过在查询中的类型定义后面附带默认值的方式,将默认值赋给变量。
query HeroNameAndFriends($episode: Episode = "JEDI") { hero(episode: $episode) { name
friends { name
} }}
指令(Directives)
我们可能需要使用变量动态地改变我们查询的结构,如下例:
// 定义变量
{
"episode": "JEDI", "withFriends": false}
// 请求
query Hero($episode: Episode, $withFriends: Boolean!) {
hero(episode: $episode) { name
friends @include(if: $withFriends) { name
} }}
// 响应
{
"data": { "hero": { "name": "R2-D2" } }}
我们用了 GraphQL 中一种称作指令的新特性。一个指令可以附着在字段或者片段包含的字段上。
GraphQL 的核心规范包含两个指令:
- @include(if: Boolean) 仅在参数为 true 时,包含此字段。
- @skip(if: Boolean) 如果参数为 true,跳过此字段。
变更(Mutations)
GraphQL 建了一个约定来规范任何写入的操作,都应该显式通过变更(mutation)来发送。
// 定义变量
{
"ep": "JEDI", "review": { "stars": 5, "commentary": "This is a great movie!" }}
// 变更请求
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary }}
// 响应
{
"data": { "createReview": { "stars": 5, "commentary": "This is a great movie!" } }}
一个变更也能包含多个字段,一如查询。查询和变更之间名称之外的一个重要区别是:
查询字段时,是并行执行,而变更字段时,是线性执行,一个接着一个。
这意味着如果我们一个请求中发送了两个 incrementCredits 变更,第一个保证在第二个之前执行,以确保我们不会出现竞态。
内联片段(Inline Fragments)
如果你查询的字段返回的是接口或者联合类型,那么你可能需要使用内联片段来取出下层具体类型的数据:
// 定义变量
{
"ep": "JEDI"}
// 请求
query HeroForEpisode($ep: Episode!) { hero(episode: $ep) { name
... on Droid { primaryFunction } ... on Human { height
} }}
// 响应
{
"data": { "hero": { "name": "R2-D2", "primaryFunction": "Astromech" } }}
这个查询中,hero 字段返回 Character 类型,取决于 episode 参数,其可能是 Human 或者 Droid 类型。在直接选择的情况下,你只能请求 Character 上存在的字段,譬如 name。
如果要请求具体类型上的字段,你需要使用一个类型条件内联片段。因为第一个片段标注为 ... on Droid,primaryFunction 仅在 hero 返回的 Character 为 Droid 类型时才会执行。同理适用于 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 返回了一个联合类型,其可能是三种选项之一。没有 __typename 字段的情况下,几乎不可能在客户端分辨开这三个不同的类型。