查询和变更
¥Queries and Mutations
在此页面上,你将详细了解如何查询 GraphQL 服务器。
¥On this page, you’ll learn in detail about how to query a GraphQL server.
字段
¥Fields
最简单的是,GraphQL 是询问对象上的特定字段。让我们首先看一个非常简单的查询以及运行它时得到的结果:
¥At its simplest, GraphQL is about asking for specific fields on objects. Let’s start by looking at a very simple query and the result we get when we run it:
你可以立即看到查询与结果具有完全相同的形状。这对于 GraphQL 来说至关重要,因为你总是会得到你期望的结果,并且服务器确切地知道客户端请求的字段。
¥You can see immediately that the query has exactly the same shape as the result. 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.
字段 name
返回 String
类型,在本例中是星球大战主要英雄的名字,"R2-D2"
。
¥The field name
returns a String
type, in this case the name of the main hero of Star Wars, "R2-D2"
.
哦,还有一件事 - 上面的查询是交互式的。这意味着你可以根据需要更改它并查看新结果。尝试将
appearsIn
字段添加到查询中的hero
对象,并查看新结果。¥Oh, one more thing - the query above is interactive. That means you can change it as you like and see the new result. Try adding an
appearsIn
field to thehero
object in the query, and see the new result.
在前面的示例中,我们只是询问英雄的名字,它返回一个字符串,但字段也可以引用对象。在这种情况下,你可以对该对象进行字段的子选择。GraphQL 查询可以遍历相关对象及其字段,让客户端在一次请求中获取大量相关数据,而不是像经典 REST 结构中那样进行多次往返。
¥In the previous example, we just asked for the name of our hero which returned a String, but fields can also refer to Objects. In that case, you can make a sub-selection of fields for that object. 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 both single items or lists of items; however, we know which one to expect based on what is indicated in the schema.
参数
¥Arguments
如果我们唯一能做的就是遍历对象及其字段,那么 GraphQL 将已经成为一种非常有用的数据获取语言。但是,当你添加将参数传递给字段的功能时,事情会变得更加有趣。
¥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.
在像 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 scalar fields, to implement data transformations once on the server, instead of on every client separately.
参数可以有许多不同的类型。在上面的示例中,我们使用了枚举类型,它表示有限选项集之一(在本例中为长度单位,METER
或 FOOT
)。GraphQL 附带一组默认类型,但 GraphQL 服务器也可以声明自己的自定义类型,只要它们可以序列化为你的传输格式即可。
¥Arguments can be of many different types. In the above example, we have used an Enumeration 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 its own custom types, as long as they can be serialized into your transport format.
¥Read more about the GraphQL type system here.
别名
¥Aliases
如果你目光敏锐,你可能已经注意到,由于结果对象字段与查询中的字段名称匹配,但不包含参数,因此你无法直接使用不同参数查询同一字段。这就是为什么你需要别名 - 它们允许你将字段的结果重命名为你想要的任何名称。
¥If you have a sharp eye, you may have noticed that, since the result object fields match the name of the field 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.
在上面的示例中,两个 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.
片段
¥Fragments
假设我们的应用中有一个相对复杂的页面,它让我们可以并排查看两个英雄以及他们的朋友。你可以想象这样的查询很快就会变得复杂,因为我们需要至少重复一次字段 - 比较的每一侧各一个。
¥Let’s say we had 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 包含称为片段的可重用单元的原因。片段允许你构造字段集,然后将它们包含在你需要的查询中。以下是如何使用片段解决上述情况的示例:
¥That’s why GraphQL includes reusable units called fragments. Fragments let you construct sets of fields, and then include them in queries where you need to. Here’s an example of how you could solve the above situation using fragments:
你可以看到,如果字段重复,上面的查询将非常重复。片段的概念经常用于将复杂的应用数据需求拆分为更小的块,特别是当你需要将大量具有不同片段的 UI 组件组合到一个初始数据获取中时。
¥You can see how the above query would be pretty repetitive if the fields were repeated. The concept of fragments is frequently used to split complicated application data requirements into smaller chunks, especially when you need to combine lots of 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 query or mutation. See variables.
操作名称
¥Operation name
在上面的几个示例中,我们一直使用速记语法,其中省略了 query
关键字和查询名称,但在生产应用中,使用它们可以使我们的代码不那么模糊。
¥In several of the examples above we have been using a shorthand syntax where we omit both the query
keyword and the query name, but in production apps it’s useful to use these to make our code less ambiguous.
以下是包含关键字 query
作为操作类型和 HeroNameAndFriends
作为操作名称的示例:
¥Here’s an example that includes the keyword query
as operation type and HeroNameAndFriends
as operation name :
操作类型可以是查询、变更或订阅,并描述你打算执行的操作类型。除非你使用查询速记语法,否则操作类型是必需的,在这种情况下,你无法为操作提供名称或变量定义。
¥The operation type is either query, mutation, or subscription and describes what type of operation you’re intending to do. The operation type is required unless you’re using the query shorthand syntax, in which case you can’t supply a name or variable definitions for your operation.
操作名称是你的操作的有意义且明确的名称。仅在多操作文档中需要它,但鼓励使用它,因为它对调试和服务器端日志记录非常有帮助。当出现问题时(你在网络日志或 GraphQL 服务器日志中看到错误),通过名称识别代码库中的查询比尝试破译内容更容易。将此视为你最喜欢的编程语言中的函数名称。例如,在 JavaScript 中,我们可以轻松地仅使用匿名函数,但是当我们为函数命名时,可以更轻松地跟踪它、调试代码并在调用时记录它。同样,GraphQL 查询和变更名称以及片段名称可以成为服务器端有用的调试工具,用于识别不同的 GraphQL 请求。
¥The operation name is a meaningful and explicit name for your operation. It is only required in multi-operation documents, but its use is encouraged because it is very 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. 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.
变量
¥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:
-
将查询中的静态值替换为
$variableName
¥Replace the static value in the query with
$variableName
-
将
$variableName
声明为查询接受的变量之一¥Declare
$variableName
as one of the variables accepted by the query -
在单独的、特定于传输的(通常是 JSON)变量字典中传递
variableName: value
¥Pass
variableName: value
in the separate, transport-specific (usually JSON) variables dictionary
整体看起来是这样的:
¥Here’s what it looks like all together:
现在,在我们的客户端代码中,我们可以简单地传递一个不同的变量,而不需要构造一个全新的查询。一般来说,这也是一个很好的做法,用于指示查询中的哪些参数应该是动态的 - 我们永远不应该进行字符串插值来根据用户提供的值构造查询。
¥Now, in our client code, we can simply pass a different variable rather than needing to construct an entirely new query. This is also in general 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 scalars, enums, or input object types. So if you want to pass a complex object into a field, you need to know what input type that matches on the server. Learn more about input object types on the Schema page.
变量定义可以是可选的或必需的。在上面的例子中,由于 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.
要了解有关这些变量定义语法的更多信息,学习 GraphQL 结构语言 很有用。结构语言在 结构和类型页面 上有详细说明。
¥To learn more about the syntax for these variable definitions, it’s useful to learn the GraphQL schema language. The schema language 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.
指令
¥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 pretty big 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:
尝试编辑上面的变量以将 true
传递给 withFriends
,并查看结果如何变化。
¥Try editing the variables above to instead pass true
for withFriends
, and see how the result changes.
我们需要使用 GraphQL 中的一个新功能,称为指令。指令可以附加到字段或片段包含,并且可以以服务器期望的任何方式影响查询的执行。核心 GraphQL 规范恰好包含两个指令,任何符合规范的 GraphQL 服务器实现都必须支持这两个指令:
¥We needed to use a new feature in GraphQL called a directive. A directive can be attached to a field or fragment inclusion, 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 istrue
. -
@skip(if: Boolean)
如果参数为true
,则跳过此字段。¥
@skip(if: Boolean)
Skip this field if the argument istrue
.
指令对于摆脱需要执行字符串操作以在查询中添加和删除字段的情况很有用。服务器实现还可以通过定义全新的指令来添加实验性功能。
¥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.
变更
¥Mutations
大多数 GraphQL 的讨论都集中在数据获取上,但任何完整的数据平台也需要一种修改服务器端数据的方法。
¥Most discussions of GraphQL focus on data fetching, but any complete data platform needs a way to modify server-side data as well.
在 REST 中,任何请求最终都可能会对服务器造成一些副作用,但按照惯例,建议不要使用 GET
请求来修改数据。GraphQL 类似 - 从技术上讲,任何查询都可以实现以引起数据写入。然而,建立一个约定是有用的,即任何导致写入的操作都应该通过变更显式发送。
¥In REST, any request might end up causing some side-effects on the server, but by convention it’s suggested that one doesn’t use GET
requests to modify data. GraphQL is similar - technically any query could be implemented to cause a data write. However, it’s useful to establish a convention that any operations that cause writes should be sent explicitly via a mutation.
就像在查询中一样,如果变更字段返回对象类型,你可以请求嵌套字段。这对于在更新后获取对象的新状态非常有用。让我们看一个简单的变更示例:
¥Just like in queries, if the mutation field returns an object type, you can ask for nested fields. This can be useful for fetching the new state of an object after an update. Let’s look at a simple example mutation:
请注意 createReview
字段如何返回新创建的评论的 stars
和 commentary
字段。这在改变现有数据时特别有用,例如,在增加字段时,因为我们可以通过一个请求来改变和查询该字段的新值。
¥Note how createReview
field returns the stars
and commentary
fields of the newly created review. This is especially useful when mutating existing data, for example, when incrementing a field, since we can mutate and query the new value of the field with one request.
你可能还注意到,在此示例中,我们传入的 review
变量不是标量。它是一种输入对象类型,一种可以作为参数传入的特殊对象类型。在结构页面上了解有关输入类型的更多信息。
¥You might also notice that, in this example, the review
variable we passed in is not a scalar. It’s an input object type, a special kind of object type that can be passed in as an argument. Learn more about input types on the Schema page.
变更的多个字段
¥Multiple fields in mutations
一个变更可以包含多个字段,就像查询一样。除了名称之外,查询和变更之间还有一个重要区别:
¥A mutation can contain multiple fields, just like a query. There’s one important distinction between queries and mutations, other than the name:
查询字段是并行执行的,而变更字段是串行运行的,一个接一个。
¥While query fields are executed in parallel, mutation fields run in series, one after the other.
这意味着,如果我们在一个请求中发送两个 incrementCredits
变更,则保证第一个变更在第二个请求开始之前完成,从而确保我们不会最终陷入竞争状态。
¥This means that if we send two incrementCredits
mutations in one request, the first is guaranteed to finish before the second begins, ensuring that we don’t end up with a race condition with ourselves.
内联片段
¥Inline Fragments
与许多其他类型系统一样,GraphQL 结构包含定义接口和联合类型的能力。在结构指南中了解它们。
¥Like many other type systems, GraphQL schemas include the ability to define interfaces and union types. Learn about them in the schema guide.
如果你正在查询返回接口或联合类型的字段,则需要使用内联片段来访问底层具体类型的数据。通过一个例子最容易看出:
¥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:
在此查询中,hero
字段返回类型 Character
,该类型可能是 Human
或 Droid
,具体取决于 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 that exist on the Character
interface, such as name
.
要请求具体类型的字段,你需要使用带有类型条件的内联片段。因为第一个片段被标记为 ... on Droid
,所以只有当从 hero
返回的 Character
是 Droid
类型时,才会执行 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 服务返回什么类型,你需要某种方法来确定如何在客户端处理该数据。GraphQL 允许你在查询中的任意点请求元字段 __typename
,以获取该点的对象类型名称。
¥Given that there are some situations where you don’t know what type you’ll get back from the GraphQL service, you need some way to determine how to handle that data on the client. 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.
在上面的查询中,search
返回一个联合类型,可以是三个选项之一。如果没有 __typename
字段,就不可能从客户端区分不同的类型。
¥In the above query, search
returns a union type that can be one of three options. It would be impossible to tell apart the different types from the client without the __typename
field.
GraphQL 服务提供了一些元字段,其余字段用于公开 内省 系统。
¥GraphQL services provide a few meta fields, the rest of which are used to expose the Introspection system.