指令是 GraphQL 最好 - 也是最不为人知 - 的功能之一。
让我们探索如何使用 GraphQL 内置的模式和操作指令,所有符合 GraphQL 规范的 API 都必须实现这些指令。 如果你正在使用动态前端,它们非常有用,因为你可以控制根据用户交互内容来减少响应负载。
指令概述
让我们想象一个应用程序,您可以在其中选择自定义表中显示的列。 如果你隐藏了两三列,那么就没有必要获取这些单元格的数据。 但是,使用 GraphQL 指令,我们可以选择包含或跳过这些字段。
GraphQL 规范定义了什么是指令以及它们的使用位置。 具体来说,指令可以由消费者操作(例如查询)和底层模式本身使用。 或者,简单地说,指令要么基于模式,要么基于操作。 模式指令 在生成模式时使用,而 操作指令 在执行查询时运行。
简而言之,指令可用于元数据、运行时提示、运行时解析(例如以特定格式返回日期)和扩展描述(例如弃用)目的。
四种指令
GraphQL 在 规范工作草案中定义了四种主要指令,其中一种尚未发布为工作草案。
@include
@skip
@deprecated
@specifiedBy
(工作草案)
如果你密切关注 GraphQL,你还会注意到两个额外的指令被合并到 JavaScript 实现中,你今天就可以尝试使用它们 - @stream
和 @defer
。 这些指令尚未成为正式规范的一部分,而社区正在真实世界的应用程序中对它们进行测试。
@include
@include
指令,顾名思义,允许我们通过传递一个 if
参数来有条件地包含字段。 由于它是条件性的,所以最好在查询中使用 变量 来检查真值性。
例如,如果以下示例中的变量为真值,则 name
字段将包含在查询响应中。
query getUsers($showName: Boolean) {
users {
id
name @include(if: $showName)
}
}
相反,我们可以选择不包含该字段,方法是将变量 $showName
作为 false
以及查询一起传递。 我们还可以为 $showName
变量指定一个默认值,因此无需在每次请求时都传递它
query getUsers($showName: Boolean = true) {
users {
id
name @include(if: $showName)
}
}
@skip
我们可以用 @skip
指令来表达与刚才相同的事情。 如果该值为真值,那么它将跳过该字段。
query getUsers($hideName: Boolean) {
users {
id
name @skip(if: $hideName)
}
}
虽然这对于单个字段非常有效,但有时我们可能需要包含或跳过多个字段。 我们可以像这样在多行中重复使用 @include
和 @skip
query getUsers($includeFields: Boolean) {
users {
id
name @include(if: $includeFields)
email @include(if: $includeFields)
role @include(if: $includeFields)
}
}
@skip
和 @include
指令都可以用于字段、片段扩展和内联片段,这意味着我们可以使用内联片段来执行其他操作,例如
query getUsers($excludeFields: Boolean) {
users {
id
... on User @skip(if: $excludeFields) {
name
email
role
}
}
}
如果片段已定义,我们也可以在将片段扩展到查询时使用 @skip
和 @include
fragment User on User {
name
email
role
}
query getUsers($excludeFields: Boolean) {
users {
id
...User @skip(if: $excludeFields)
}
}
@deprecated
@deprecated
指令仅出现在模式中,而不是用户作为查询的一部分提供的,就像我们在上面看到的那样。 相反,@deprecated
指令是由维护 GraphQL API 模式的开发人员指定的。
作为用户,如果我们尝试获取模式中已弃用的字段,我们将收到如下警告,它提供上下文帮助。

为了标记一个字段为已弃用,我们需要在模式定义语言 (SDL) 中使用 @deprecated
指令,在参数中传递一个 reason
,就像这样
type User {
id: ID!
title: String @deprecated(reason: "Use name instead")
name: String!
email: String!
role: Role
}
如果我们将它与 @include
指令配对,我们可以根据查询变量有条件地获取已弃用的字段
fragment User on User {
title @include(if: $includeDeprecatedFields)
name
email
role
}
query getUsers($includeDeprecatedFields: Boolean! = false) {
users {
id
...User
}
}
@specifiedBy
@specifiedBy
是第四个指令,目前是工作草案的一部分。 它将被 自定义标量 实现使用,并接受一个 url
参数,该参数应该指向标量规范。
例如,如果我们添加一个 用于电子邮件地址的自定义标量,我们希望将 URL 传递到作为该部分使用的正则表达式规范。 使用最后一个示例和 RFC #822 中定义的提案,EmailAddress
的标量将被定义在模式中,如下所示
scalar EmailAddress @specifiedBy(url: "https://www.w3.org/Protocols/rfc822/")
建议自定义指令具有前缀名称,以防止与其他添加的指令发生冲突。 如果你正在寻找自定义指令的示例以及它的创建方式,请查看 GraphQL 公共模式。 它是一个自定义 GraphQL 指令,具有代码和模式优先支持,用于注释哪些 API 可以公开使用。
总结
所以,这就是对 GraphQL 指令的高级概述。 我再次认为指令是某种默默无闻的英雄,被其他 GraphQL 功能所掩盖。 我们已经通过 GraphQL 模式获得了很大的控制权,而指令让我们能够更加细粒度地控制,以便从查询中获得我们想要的确切结果。 这就是使 GraphQL API 如此快速,并最终更易于使用的效率。
如果你正在构建 GraphQL API,那么一定要将这些指令包含在内省查询中。 包含它们不仅使开发人员能够获得额外控制的好处,而且还能够获得更好的整体开发体验。 想象一下,正确地 @deprecate
字段,使开发人员无需离开代码即可知道该怎么做,这将是多么有用? 这本身就很有力。
标题图片由 Isabel Gonçalves 在 Unsplash 上提供