学习教程
查询

查询

¥Queries

Learn how to fetch data from a GraphQL server

GraphQL 支持三种主要操作类型 - 查询、突变和订阅。我们已经在本指南中看到了几个基本查询的示例,在本页中,你将详细了解如何使用查询操作的各种功能从服务器读取数据。

¥GraphQL supports three main operation types—queries, mutations, and subscriptions. We have already seen several examples of basic queries in this guide, and on this page, you’ll learn in detail how to use the various features of query operations to read data from a server.

字段

¥Fields

最简单的说,GraphQL 是关于在对象上请求特定的 fields。让我们首先查看模式中 Query 类型上定义的 hero 字段:

¥At its simplest, GraphQL is about asking for specific fields on objects. Let’s start by looking at the hero field that’s defined on the Query type in the schema:

type Query {
  hero: Character
}

我们可以看到查询时得到的结果:

¥We can see what result we get when we query it:

Operation
Response

创建 GraphQL 文档时,我们始终以 根操作类型(本例中为 Query 对象类型)开头,因为它是 API 的入口点。从那里,我们必须指定我们感兴趣的字段选择集,一直到它们的叶值,这些叶值将是标量或枚举类型。字段 name 返回 String 类型,在本例中是星球大战主要英雄的名字,"R2-D2"

¥When creating a GraphQL document we always start with a root operation type (the Query Object type for this example) because it serves as an entry point to the API. From there we must specify the selection set of fields we are interested in, all the way down to their leaf values which will be Scalar or Enum types. The field name returns a String type, in this case the name of the main hero of Star Wars, "R2-D2".

GraphQL 规范表示请求的结果将在响应中的顶层 data 键上返回。如果请求引发任何错误,则会提供有关顶层 errors 键上出现问题的信息。从那里,你可以看到结果具有与查询相同的形状。这对于 GraphQL 来说至关重要,因为你总是会得到你期望的结果,并且服务器确切地知道客户端请求的字段。

¥The GraphQL specification indicates that a request’s result will be returned on a top-level data key in the response. If the request raised any errors, there will be information about what went wrong on a top-level errors key. From there, you can see that the result has the same shape as the query. This is essential to GraphQL, because you always get back what you expect, and the server knows exactly what fields the client is asking for.

在前面的例子中,我们只是询问了英雄的名字,它返回了 String,但字段也可以返回对象类型(及其列表)。在这种情况下,你可以对该对象类型的字段进行子选择:

¥In the previous example, we just asked for the name of our hero which returned a String, but fields can also return Object types (and lists thereof). In that case, you can make a sub-selection of fields for that Object type:

Operation
Response

GraphQL 查询可以遍历相关对象及其字段,让客户端在一次请求中获取大量相关数据,而不是像经典 REST 结构中那样进行多次往返。

¥GraphQL queries can traverse related objects and their fields, letting clients fetch lots of related data in one request, instead of making several roundtrips as one would need in a classic REST architecture.

请注意,在此示例中,friends 字段返回一个项目数组。GraphQL 查询对于单个项目或项目列表看起来相同;然而,我们根据结构中指示的内容知道应该期待哪一个。

¥Note that in this example, the friends field returns an array of items. GraphQL queries look the same for single items or lists of items; however, we know which one to expect based on what is indicated in the schema.

参数

¥Arguments

如果我们唯一能做的就是遍历对象及其字段,那么 GraphQL 将已经成为一种非常有用的数据获取语言。但当你添加将 arguments 传递给字段的能力时,事情会变得更加有趣:

¥If the only thing we could do was traverse objects and their fields, GraphQL would already be a very useful language for data fetching. But when you add the ability to pass arguments to fields, things get much more interesting:

type Query {
  droid(id: ID!): Droid
}

然后,客户端必须使用查询提供所需的 id 值:

¥The client must then provide the required id value with the query:

Operation
Response

在 REST 这样的系统中,你只能传递一组参数 - 请求中的查询参数和 URL 段。但在 GraphQL 中,每个字段和嵌套对象都可以获取自己的参数集,这使得 GraphQL 完全替代了多个 API 获取。

¥In a system like REST, you can only pass a single set of arguments—the query parameters and URL segments in your request. But in GraphQL, every field and nested object can get its own set of arguments, making GraphQL a complete replacement for making multiple API fetches.

你甚至可以将参数传递给输出标量类型的字段;一个用例是在服务器上实现一次数据转换,而不是在每个客户端上单独实现:

¥You can even pass arguments into fields that output Scalar types; one use case for this would be to implement data transformations once on the server, instead of on every client separately:

Operation
Response

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

¥Arguments can be of many different types. In the above example, we have used an Enum type, which represents one of a finite set of options (in this case, units of length, either METER or FOOT). GraphQL comes with a default set of types, but a GraphQL server can also declare custom types, as long as they can be serialized into your transport format.

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

¥Read more about the GraphQL type system here.

操作类型和名称

¥Operation type and name

在上面的例子中,我们一直在使用简写语法,在操作的选择集之前省略 query 关键字。除了明确指定操作类型之外,我们还可以添加唯一的操作名称,这在生产应用中很有用,因为它使调试和跟踪更容易。

¥In the examples above we have been using a shorthand syntax where we omit the query keyword before the operation’s selection set. In addition to specifying the operation type explicitly, we can also add a unique operation name, which is useful in production apps because it makes debugging and tracing easier.

以下是包含 query 关键字作为操作类型和 HeroNameAndFriends 作为操作名称的示例:

¥Here’s an example that includes the query keyword as the operation type and HeroNameAndFriends as the operation name:

Operation
Response

操作类型为 querymutationsubscription,描述你打算执行的操作类型。除非你使用查询的简写语法(它始终是突变和订阅所必需的),否则此关键字是必需的。此外,如果你希望为操作提供名称,则还必须指定操作类型。

¥The operation type is either query, mutation, or subscription and describes what type of operation you intend to do. This keyword is required unless you’re using the shorthand syntax for queries (it is always required for mutations and subscriptions). Additionally, if you wish to provide a name for your operation, then you must specify the operation type as well.

操作名称是你为操作指定的明确名称;你应该选择一个有意义的名字。在一个文档中发送多个操作时需要这样做,但即使只发送一个操作,也鼓励这样做,因为操作名称有助于调试和服务器端日志记录。当出现问题时(你会在网络日志或 GraphQL 服务器日志中看到错误),通过名称识别代码库中的查询比尝试解读内容更容易。

¥The operation name is an explicit name that you assign to your operation; you should pick a meaningful name. It is required when sending multiple operations in one document, but even if you’re only sending one operation it’s encouraged because operation names are helpful for debugging and server-side logging. When something goes wrong (you see errors either in your network logs or in the logs of your GraphQL server) it is easier to identify a query in your codebase by name instead of trying to decipher the contents.

将此视为你最喜欢的编程语言中的函数名称。例如,在 JavaScript 中,我们只能轻松地使用匿名函数,但是当我们为函数命名时,更容易跟踪它、调试我们的代码并在调用时记录它。同样,GraphQL 查询和变更名称以及片段名称可以成为服务器端有用的调试工具,用于识别不同的 GraphQL 请求。

¥Think of this just like a function name in your favorite programming language. For example, in JavaScript, we can easily work only with anonymous functions, but when we give a function a name, it’s easier to track it down, debug our code, and log when it’s called. In the same way, GraphQL query and mutation names, along with fragment names, can be a useful debugging tool on the server side to identify different GraphQL requests.

别名

¥Aliases

如果你眼光敏锐,你可能已经注意到,由于结果对象字段与查询中的字段名称匹配但不包含参数,因此你不能直接使用不同的参数查询同一字段。这就是你需要 aliases 的原因 - 它们允许你将字段的结果重命名为你想要的任何名称。

¥If you have a sharp eye, you may have noticed that, since the result object fields match the name of the fields in the query but don’t include arguments, you can’t directly query for the same field with different arguments. That’s why you need aliases—they let you rename the result of a field to anything you want.

Operation
Response

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

¥In the above example, the two hero fields would have conflicted, but since we can alias them to different names, we can get both results in one request.

变量

¥Variables

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

¥So far, we have been writing all of our arguments inside the query string. But in most applications, the arguments to fields will be dynamic. For example, there might be a dropdown that lets you select which Star Wars episode you are interested in, or a search field, or a set of filters.

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

¥It wouldn’t be a good idea to pass these dynamic arguments directly in the query string, because then our client-side code would need to dynamically manipulate the query string at runtime, and serialize it into a GraphQL-specific format. Instead, GraphQL has a first-class way to factor dynamic values out of the query and pass them as a separate dictionary. These values are called variables.

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

¥When we start working with variables, we need to do three things:

  1. 将查询中的静态值替换为 $variableName

    ¥Replace the static value in the query with $variableName

  2. $variableName 声明为查询接受的变量之一

    ¥Declare $variableName as one of the variables accepted by the query

  3. 在单独的、特定于传输的(通常是 JSON)变量字典中传递 variableName: value

    ¥Pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary

整体看起来是这样的:

¥Here’s what it looks like all together:

Operation
Variables
Response

你必须在 GraphQL 文档中指定操作类型和名称才能使用变量。

¥You must specify an operation type and name in a GraphQL document to use variables.

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

¥Now, in our client code, we can simply pass a different variable rather than needing to construct an entirely new query. In general, this is also a good practice for denoting which arguments in our query are expected to be dynamic—we should never be doing string interpolation to construct queries from user-supplied values.

变量定义

¥Variable definitions

变量定义是上面查询中类似于 ($episode: Episode) 的部分。它的工作原理就像类型语言中函数的参数定义一样。它列出了所有变量,前缀为 $,后跟它们的类型,在本例中为 Episode

¥The variable definitions are the part that looks like ($episode: Episode) in the query above. It works just like the argument definitions for a function in a typed language. It lists all of the variables, prefixed by $, followed by their type, in this case, Episode.

所有声明的变量必须是标量、枚举或输入对象类型。因此,如果你想将复杂对象传递到字段中,则需要知道服务器上的 输入类型 与其匹配的内容。

¥All declared variables must be either Scalar, Enum, or Input Object types. So if you want to pass a complex object into a field, you need to know what input type matches it on the server.

变量定义可以是可选的或必需的。在上面的例子中,由于 Episode 类型旁边没有 !,所以它是可选的。但是,如果你将变量传递到的字段需要非空参数,则该变量也必须是必需的。

¥Variable definitions can be optional or required. In the case above, since there isn’t an ! next to the Episode type, it’s optional. But if the field you are passing the variable into requires a non-null argument, then the variable has to be required as well.

要了解有关这些变量语法的更多信息定义,学习模式定义语言(SDL)很有用,它在 结构和类型页面 上有详细解释。

¥To learn more about the syntax for these variable definitions, it’s useful to learn schema definition language (SDL), which is explained in detail on the Schemas and Types page.

默认变量

¥Default variables

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

¥Default values can also be assigned to the variables in the query by adding the default value after the type declaration:

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

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

¥When default values are provided for all variables, you can call the query without passing any variables. If any variables are passed as part of the variables dictionary, they will override the defaults.

片段

¥Fragments

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

¥Let’s say we have a relatively complicated page in our app, which lets us look at two heroes side by side, along with their friends. You can imagine that such a query could quickly get complicated because we would need to repeat the fields at least once—one for each side of the comparison.

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

¥That’s why GraphQL includes reusable units called fragments. Fragments let you construct sets of fields, and then include them in queries where needed. Here’s an example of how you could solve the above situation using fragments:

Operation
Response

你可以看到,如果我们不能使用片段,上述查询将非常重复。片段的概念经常用于将复杂的应用数据需求拆分成更小的块,特别是当你需要将具有不同片段的许多 UI 组件组合成一个初始数据提取时。

¥You can see how the above query would be pretty repetitive if we weren’t able to use fragments. The concept of fragments is frequently used to split complicated application data requirements into smaller chunks, especially when you need to combine many UI components with different fragments into one initial data fetch.

在片段内使用变量

¥Using variables inside fragments

片段也可以访问操作中声明的变量:

¥It is possible for fragments to access variables declared in the operation as well:

Operation
Variables
Response

内联片段

¥Inline Fragments

与许多其他类型系统一样,GraphQL 模式包括定义接口和联合类型的能力。你可以在 模式和类型页面。 上了解有关它们的更多信息

¥Like many other type systems, GraphQL schemas include the ability to define Interface and Union types. You can learn more about them on the Schemas and Types page.

如果你正在查询返回接口或联合类型的字段,则需要使用内联片段来访问底层具体类型的数据。通过一个例子最容易看出:

¥If you are querying a field that returns an Interface or a Union type, you will need to use inline fragments to access data on the underlying concrete type. It’s easiest to see with an example:

Operation
Variables
Response

在此查询中,hero 字段返回类型 Character,该类型可能是 HumanDroid,具体取决于 episode 参数。在直接选择中,你只能请求 Character 接口上的字段,例如 name

¥In this query, the hero field returns the type Character, which might be either a Human or a Droid depending on the episode argument. In the direct selection, you can only ask for fields on the Character interface, such as name.

要请求具体类型的字段,你需要使用带有类型条件的内联片段。因为第一个片段被标记为 ... on Droid,所以只有当从 hero 返回的 CharacterDroid 类型时,才会执行 primaryFunction 字段。对于 Human 类型的 height 字段也是如此。

¥To ask for a field on the concrete type, you need to use an inline fragment with a type condition. Because the first fragment is labeled as ... on Droid, the primaryFunction field will only be executed if the Character returned from hero is of the Droid type. Similarly for the height field for the Human type.

命名片段也可以以相同的方式使用,因为命名片段总是附加一个类型。

¥Named fragments can also be used in the same way, since a named fragment always has a type attached.

元字段

¥Meta fields

正如我们在 联合类型 中看到的那样,在某些情况下,你不知道从 GraphQL 服务返回什么类型,因此你需要某种方法来确定如何在客户端上处理该数据。

¥As we have seen with Union types, there are some situations where you don’t know what type you’ll get back from the GraphQL service so you need some way to determine how to handle that data on the client.

GraphQL 允许你在查询中的任何点请求 __typename(元字段),以获取该点的对象类型的名称:

¥GraphQL allows you to request __typename, a meta field, at any point in a query to get the name of the Object type at that point:

Operation
Response

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

¥In the above query, search returns a Union type that can be one of three options. Without the __typename field, it would be impossible for a client to tell the different types apart.

所有以两个下划线(__)开头的字段名称都由 GraphQL 保留。除了 __typename,GraphQL 服务还提供 __schema__type 元字段,它们公开了 introspection 系统。

¥All field names beginning with two underscores (__) are reserved by GraphQL. In addition to __typename, GraphQL services provide the __schema and __type meta-fields which expose the introspection system.

指令

¥Directives

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

¥We discussed above how variables enable us to avoid doing manual string interpolation to construct dynamic queries. Passing variables in arguments solves a large class of these problems, but we might also need a way to dynamically change the structure and shape of our queries using variables. For example, we can imagine a UI component that has a summarized and detailed view, where one includes more fields than the other.

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

¥Let’s construct a query for such a component:

Operation
Variables
Response

尝试编辑上面的变量以将 true 传递给 withFriends,并查看结果如何变化。

¥Try editing the variables above to instead pass true for withFriends, and see how the result changes.

我们需要使用 GraphQL 中称为 directive 的功能。具体来说,客户端可以将可执行指令附加到字段或片段包含中,并且可以以服务器希望的任何方式影响查询的执行。核心 GraphQL 规范恰好包含两个指令,任何符合规范的 GraphQL 服务器实现都必须支持这两个指令:

¥We needed to use a feature in GraphQL called a directive. Specifically, an executable directive can be attached to a field or fragment inclusion by a client, and can affect execution of the query in any way the server desires. The core GraphQL specification includes exactly two directives, which must be supported by any spec-compliant GraphQL server implementation:

  • @include(if: Boolean) 仅当参数为 true 时才在结果中包含此字段。

    ¥@include(if: Boolean) Only include this field in the result if the argument is true.

  • @skip(if: Boolean) 如果参数为 true,则跳过此字段。

    ¥@skip(if: Boolean) Skip this field if the argument is true.

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

¥Directives can be useful to get out of situations where you otherwise would need to do string manipulation to add and remove fields in your query. Server implementations may also add experimental features by defining completely new directives.

正在寻找有关如何定义可用于注释 GraphQL 模式中的类型、字段或参数的指令的信息?有关定义和使用类型系统指令的更多信息,请参阅 模式和类型页面

¥Looking for information on how to define directives that can be used to annotate the types, fields, or arguments in your GraphQL schema? See the Schemas and Types page for more information on defining and using type system directives.

后续步骤

¥Next steps

回顾我们学到的关于突变的知识:

¥To recap what we’ve learned about queries:

  • 读取数据的 GraphQL 操作从 query 根操作类型开始,并遍历选择集中的字段直至叶值,这些叶值将是标量或枚举类型

    ¥A GraphQL operation that reads data starts at the query root operation type and traverses the fields in the selection set down to the leaf values, which will be Scalar or Enum types

  • 字段可以接受改变该字段输出的参数

    ¥Fields can accept arguments that alter the output of that field

  • 操作可以使用 querymutationsubscription 关键字来指示其类型

    ¥Operations can use the query, mutation, or subscription keyword to indicate their type

  • 仅对于某些查询操作,可以省略操作类型关键字

    ¥The operation type keyword can be omitted for certain query operations only

  • 操作应具有唯一的名称,这使请求更具表现力并有助于调试

    ¥Operations should be given unique names, which make requests more expressive and help with debugging

  • 字段别名允许你重命名响应键,在同一查询中多次包含相同的字段,并为别名字段提供不同的参数

    ¥Field aliases allow you to rename response keys, include the same field multiple times in the same query, and provide different arguments to the aliased fields

  • 变量前面有 $ 字符,可用于为字段参数提供动态值

    ¥Variables are preceded by the $ character and can be used to provide dynamic values to field arguments

  • 片段是可重复使用的字段选择集,可以根据需要在多个查询中使用

    ¥A fragment is a reusable selection set of fields that can be used as needed in multiple queries

  • 可执行指令可以应用于查询,以在服务器上执行 GraphQL 查询时更改其结果

    ¥Executable directive can be applied to queries to change the result of a GraphQL query when it’s executed on the server

  • 所有符合规范的 GraphQL 服务器都包含 @include@skip 内置指令

    ¥All spec-compliant GraphQL servers include the @include and @skip built-in directives

现在我们了解了如何使用查询操作从 GraphQL 服务器读取数据的来龙去脉,是时候学习如何使用 mutations 更改数据并触发副作用了。

¥Now that we understand the ins and outs of how to read data from a GraphQL server with query operations, it’s time to learn how to change data and trigger side effects using mutations.