使用内置 GraphQL 指令

Avatar of Jamie Barton
Jamie Barton

DigitalOcean 为您的旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

指令是 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 模式的开发人员指定的。

作为用户,如果我们尝试获取模式中已弃用的字段,我们将收到如下警告,它提供上下文帮助。

A zoomed in example of a syntax-highlighted GraphQL query for getUsers, which contains a users object with id and title properties. The title property is underlined in yellow with a contextual tooltip open below it showing a warning in yellow and white that suggests using the name field instead.
在这个例子中,title 字段已被标记为已弃用,该指令提供了一个有用的提示来替换它。

为了标记一个字段为已弃用,我们需要在模式定义语言 (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çalvesUnsplash 上提供