结构和类型
¥Schemas and Types
在此页面上,你将了解有关 GraphQL 类型系统以及它如何描述可以查询的数据的所有信息。由于 GraphQL 可以与任何后端框架或编程语言一起使用,因此我们将远离特定于实现的细节,只讨论概念。
¥On this page, you’ll learn all you need to know about the GraphQL type system and how it describes what data can be queried. Since GraphQL can be used with any backend framework or programming language, we’ll stay away from implementation-specific details and talk only about the concepts.
类型系统
¥Type system
如果你以前见过 GraphQL 查询,你就会知道 GraphQL 查询语言基本上是关于选择对象上的字段。因此,例如,在以下查询中:
¥If you’ve seen a GraphQL query before, you know that the GraphQL query language is basically about selecting fields on objects. So, for example, in the following query:
-
我们从一个特殊的 “root” 对象开始
¥We start with a special “root” object
-
我们选择
hero
字段¥We select the
hero
field on that -
对于
hero
返回的对象,我们选择name
和appearsIn
字段¥For the object returned by
hero
, we select thename
andappearsIn
fields
由于 GraphQL 查询的形状与结果紧密匹配,因此你可以预测查询将返回什么,而无需了解有关服务器的太多信息。但对我们可以要求的数据进行准确的描述是很有用的 - 我们可以选择哪些字段?他们可能返回什么类型的对象?这些子对象有哪些可用字段?这就是结构的用武之地。
¥Because the shape of a GraphQL query closely matches the result, you can predict what the query will return without knowing that much about the server. But it’s useful to have an exact description of the data we can ask for - what fields can we select? What kinds of objects might they return? What fields are available on those sub-objects? That’s where the schema comes in.
每个 GraphQL 服务都定义了一组类型,这些类型完整地描述了你可以在该服务上查询的可能数据集。然后,当查询进来时,它们将根据该结构进行验证和执行。
¥Every GraphQL service defines a set of types which completely describe the set of possible data you can query on that service. Then, when queries come in, they are validated and executed against that schema.
类型语言
¥Type language
GraphQL 服务可以用任何语言编写。由于我们不能依赖特定的编程语言语法(例如 JavaScript)来讨论 GraphQL 结构,因此我们将定义自己的简单语言。我们将使用 “GraphQL 结构语言” - 它类似于查询语言,允许我们以与语言无关的方式讨论 GraphQL 结构。
¥GraphQL services can be written in any language. Since we can’t rely on a specific programming language syntax, like JavaScript, to talk about GraphQL schemas, we’ll define our own simple language. We’ll use the “GraphQL schema language” - it’s similar to the query language, and allows us to talk about GraphQL schemas in a language-agnostic way.
对象类型和字段
¥Object types and fields
GraphQL 结构最基本的组件是对象类型,它仅代表你可以从服务中获取的一种对象,以及它具有哪些字段。在 GraphQL 结构语言中,我们可以这样表示:
¥The most basic components of a GraphQL schema are object types, which just represent a kind of object you can fetch from your service, and what fields it has. In the GraphQL schema language, we might represent it like this:
type Character {
name: String!
appearsIn: [Episode!]!
}
该语言非常易读,但让我们回顾一下,以便我们可以拥有共同的词汇:
¥The language is pretty readable, but let’s go over it so that we can have a shared vocabulary:
-
Character
是 GraphQL 对象类型,这意味着它是具有某些字段的类型。结构中的大多数类型都是对象类型。¥
Character
is a GraphQL Object Type, meaning it’s a type with some fields. Most of the types in your schema will be object types. -
name
和appearsIn
是Character
类型的字段。这意味着name
和appearsIn
是唯一可以出现在对Character
类型进行操作的 GraphQL 查询的任何部分中的字段。¥
name
andappearsIn
are fields on theCharacter
type. That means thatname
andappearsIn
are the only fields that can appear in any part of a GraphQL query that operates on theCharacter
type. -
String
是内置标量类型之一 - 这些类型解析为单个标量对象,并且在查询中不能有子选择。稍后我们将详细讨论标量类型。¥
String
is one of the built-in scalar types - these are types that resolve to a single scalar object, and can’t have sub-selections in the query. We’ll go over scalar types more later. -
String!
表示该字段不可为空,这意味着 GraphQL 服务保证在你查询该字段时始终为你提供一个值。在类型语言中,我们将用感叹号来表示那些。¥
String!
means that the field is non-nullable, meaning that the GraphQL service promises to always give you a value when you query this field. In the type language, we’ll represent those with an exclamation mark. -
[Episode!]!
代表Episode
对象的数组。由于它也是不可为 null 的,因此当你查询appearsIn
字段时,你始终可以期待一个数组(包含零个或多个项目)。由于Episode!
也是不可为 null 的,因此你始终可以期望数组的每个项目都是Episode
对象。¥
[Episode!]!
represents an array ofEpisode
objects. Since it is also non-nullable, you can always expect an array (with zero or more items) when you query theappearsIn
field. And sinceEpisode!
is also non-nullable, you can always expect every item of the array to be anEpisode
object.
现在你知道 GraphQL 对象类型是什么样的,以及如何阅读 GraphQL 类型语言的基础知识。
¥Now you know what a GraphQL object type looks like, and how to read the basics of the GraphQL type language.
参数
¥Arguments
GraphQL 对象类型上的每个字段都可以有零个或多个参数,例如下面的 length
字段:
¥Every field on a GraphQL object type can have zero or more arguments, for example the length
field below:
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
所有参数均已命名。与 JavaScript 和 Python 等语言的函数采用有序参数列表不同,GraphQL 中的所有参数都专门按名称传递。在本例中,length
字段有一个定义的参数 unit
。
¥All arguments are named. Unlike languages like JavaScript and Python where functions take a list of ordered arguments, all arguments in GraphQL are passed by name specifically. In this case, the length
field has one defined argument, unit
.
参数可以是必需的,也可以是可选的。当参数是可选的时,我们可以定义一个默认值 - 如果不传递 unit
参数,则默认设置为 METER
。
¥Arguments can be either required or optional. When an argument is optional, we can define a default value - if the unit
argument is not passed, it will be set to METER
by default.
查询和变更类型
¥The Query and Mutation types
结构中的大多数类型只是普通的对象类型,但结构中有两种特殊类型:
¥Most types in your schema will just be normal object types, but there are two types that are special within a schema:
schema {
query: Query
mutation: Mutation
}
每个 GraphQL 服务都有 query
类型,可能有也可能没有 mutation
类型。这些类型与常规对象类型相同,但它们很特殊,因为它们定义了每个 GraphQL 查询的入口点。因此,如果你看到如下所示的查询:
¥Every GraphQL service has a query
type and may or may not have a mutation
type. These types are the same as a regular object type, but they are special because they define the entry point of every GraphQL query. So if you see a query that looks like:
这意味着 GraphQL 服务需要具有带有 hero
和 droid
字段的 Query
类型:
¥That means that the GraphQL service needs to have a Query
type with hero
and droid
fields:
type Query {
hero(episode: Episode): Character
droid(id: ID!): Droid
}
变更以类似的方式工作 - 你在 Mutation
类型上定义字段,这些字段可用作你可以在查询中调用的根变更字段。
¥Mutations work in a similar way - you define fields on the Mutation
type, and those are available as the root mutation fields you can call in your query.
重要的是要记住,除了结构中 “入口点” 的特殊状态之外,Query
和 Mutation
类型与任何其他 GraphQL 对象类型相同,并且它们的字段的工作方式完全相同。
¥It’s important to remember that other than the special status of being the “entry point” into the schema, the Query
and Mutation
types are the same as any other GraphQL object type, and their fields work exactly the same way.
标量类型
¥Scalar types
GraphQL 对象类型具有名称和字段,但在某些时候这些字段必须解析为一些具体数据。这就是标量类型的用武之地:它们代表查询的叶子。
¥A GraphQL object type has a name and fields, but at some point those fields have to resolve to some concrete data. That’s where the scalar types come in: they represent the leaves of the query.
在以下查询中,name
和 appearsIn
字段将解析为标量类型:
¥In the following query, the name
and appearsIn
fields will resolve to scalar types:
我们知道这一点是因为这些字段没有任何子字段 - 它们是查询的叶子。
¥We know this because those fields don’t have any sub-fields - they are the leaves of the query.
GraphQL 附带了一组开箱即用的默认标量类型:
¥GraphQL comes with a set of default scalar types out of the box:
-
Int
:有符号 32 位整数。¥
Int
: A signed 32‐bit integer. -
Float
:有符号双精度浮点值。¥
Float
: A signed double-precision floating-point value. -
String
:UTF-8 字符序列。¥
String
: A UTF‐8 character sequence. -
Boolean
:true
或false
。¥
Boolean
:true
orfalse
. -
ID
:ID 标量类型表示唯一标识符,通常用于重新获取对象或作为缓存的键。ID 类型的序列化方式与 String 相同;然而,将其定义为ID
意味着它不适合人类可读。¥
ID
: The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as anID
signifies that it is not intended to be human‐readable.
在大多数 GraphQL 服务实现中,还有一种指定自定义标量类型的方法。例如,我们可以定义一个 Date
类型:
¥In most GraphQL service implementations, there is also a way to specify custom scalar types. For example, we could define a Date
type:
scalar Date
然后由我们的实现来定义如何序列化、反序列化和验证该类型。例如,你可以指定 Date
类型应始终序列化为整数时间戳,并且你的客户端应该知道任何日期字段都应采用该格式。
¥Then it’s up to our implementation to define how that type should be serialized, deserialized, and validated. For example, you could specify that the Date
type should always be serialized into an integer timestamp, and your client should know to expect that format for any date fields.
枚举类型
¥Enumeration types
枚举类型也称为枚举,是一种特殊的标量,仅限于一组特定的允许值。这使你能够:
¥Also called Enums, enumeration types are a special kind of scalar that is restricted to a particular set of allowed values. This allows you to:
-
验证此类型的任何参数是否都是允许的值之一
¥Validate that any arguments of this type are one of the allowed values
-
通过类型系统传达字段始终是有限值集之一的信息
¥Communicate through the type system that a field will always be one of a finite set of values
GraphQL 结构语言中的枚举定义可能如下所示:
¥Here’s what an enum definition might look like in the GraphQL schema language:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
这意味着无论我们在结构中使用类型 Episode
,我们都希望它恰好是 NEWHOPE
、EMPIRE
或 JEDI
之一。
¥This means that wherever we use the type Episode
in our schema, we expect it to be exactly one of NEWHOPE
, EMPIRE
, or JEDI
.
请注意,各种语言的 GraphQL 服务实现都有自己特定于语言的方式来处理枚举。在支持枚举作为一等公民的语言中,实现可能会利用这一点;在像 JavaScript 这样不支持枚举的语言中,这些值可能会在内部映射到一组整数。但是,这些详细信息不会泄露给客户端,客户端可以完全根据枚举值的字符串名称进行操作。
¥Note that GraphQL service implementations in various languages will have their own language-specific way to deal with enums. In languages that support enums as a first-class citizen, the implementation might take advantage of that; in a language like JavaScript with no enum support, these values might be internally mapped to a set of integers. However, these details don’t leak out to the client, which can operate entirely in terms of the string names of the enum values.
列表和非空
¥Lists and Non-Null
对象类型、标量和枚举是你可以在 GraphQL 中定义的唯一类型。但是,当你在结构的其他部分或查询变量声明中使用类型时,你可以应用影响这些值验证的其他类型修饰符。让我们看一个例子:
¥Object types, scalars, and enums are the only kinds of types you can define in GraphQL. But when you use the types in other parts of the schema, or in your query variable declarations, you can apply additional type modifiers that affect validation of those values. Let’s look at an example:
type Character {
name: String!
appearsIn: [Episode]!
}
在这里,我们使用 String
类型,并通过在类型名称后添加感叹号 !
将其标记为非空。这意味着我们的服务器总是期望为此字段返回一个非空值,如果它最终得到一个空值,实际上会触发 GraphQL 执行错误,让客户端知道出了问题。
¥Here, we’re using a String
type and marking it as Non-Null by adding an exclamation mark, !
after the type name. This means that our server always expects to return a non-null value for this field, and if it ends up getting a null value that will actually trigger a GraphQL execution error, letting the client know that something has gone wrong.
在为字段定义参数时也可以使用非空类型修饰符,如果将空值作为该参数传递,无论是在 GraphQL 字符串中还是在变量中,这都会导致 GraphQL 服务器返回验证错误。
¥The Non-Null type modifier can also be used when defining arguments for a field, which will cause the GraphQL server to return a validation error if a null value is passed as that argument, whether in the GraphQL string or in the variables.
列表的工作方式类似:我们可以使用类型修饰符将类型标记为 List
,这表明该字段将返回该类型的数组。在结构语言中,这通过将类型括在方括号 [
和 ]
中来表示。对于参数来说,它的工作方式相同,其中验证步骤将期望该值的数组。
¥Lists work in a similar way: We can use a type modifier to mark a type as a List
, which indicates that this field will return an array of that type. In the schema language, this is denoted by wrapping the type in square brackets, [
and ]
. It works the same for arguments, where the validation step will expect an array for that value.
非空和列表修饰符可以组合使用。例如,你可以有一个非空字符串列表:
¥The Non-Null and List modifiers can be combined. For example, you can have a List of Non-Null Strings:
myField: [String!]
这意味着列表本身可以为空,但不能有任何空成员。例如,在 JSON 中:
¥This means that the list itself can be null, but it can’t have any null members. For example, in JSON:
myField: null // valid
myField: [] // valid
myField: ["a", "b"] // valid
myField: ["a", null, "b"] // error
现在,假设我们定义了一个非空字符串列表:
¥Now, let’s say we defined a Non-Null List of Strings:
myField: [String]!
这意味着列表本身不能为空,但它可以包含空值:
¥This means that the list itself cannot be null, but it can contain null values:
myField: null // error
myField: [] // valid
myField: ["a", "b"] // valid
myField: ["a", null, "b"] // valid
你可以根据需要任意嵌套任意数量的 Non-Null 和 List 修饰符。
¥You can arbitrarily nest any number of Non-Null and List modifiers, according to your needs.
接口
¥Interfaces
与许多类型系统一样,GraphQL 支持接口。接口是一种抽象类型,包含一组特定的字段,类型必须包含这些字段才能实现接口。
¥Like many type systems, GraphQL supports interfaces. An Interface is an abstract type that includes a certain set of fields that a type must include to implement the interface.
例如,你可以有一个接口 Character
代表星球大战三部曲中的任何角色:
¥For example, you could have an interface Character
that represents any character in the Star Wars trilogy:
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
这意味着任何实现 Character
的类型都需要具有这些精确的字段以及这些参数和返回类型。
¥This means that any type that implements Character
needs to have these exact fields, with these arguments and return types.
例如,以下是一些可能实现 Character
的类型:
¥For example, here are some types that might implement Character
:
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
你可以看到这两种类型都具有 Character
接口中的所有字段,但还引入了特定于该特定字符类型的额外字段 totalCredits
、starships
和 primaryFunction
。
¥You can see that both of these types have all of the fields from the Character
interface, but also bring in extra fields, totalCredits
, starships
and primaryFunction
, that are specific to that particular type of character.
当你想要返回一个对象或一组对象时,接口非常有用,但这些对象可能有多种不同的类型。
¥Interfaces are useful when you want to return an object or set of objects, but those might be of several different types.
例如,请注意以下查询会产生错误:
¥For example, note that the following query produces an error:
hero
字段返回类型 Character
,这意味着它可能是 Human
或 Droid
,具体取决于 episode
参数。在上面的查询中,只能查询 Character
接口上存在的字段,不包括 primaryFunction
。
¥The hero
field returns the type Character
, which means it might be either a Human
or a Droid
depending on the episode
argument. In the query above, you can only ask for fields that exist on the Character
interface, which doesn’t include primaryFunction
.
要请求特定对象类型的字段,你需要使用内联片段:
¥To ask for a field on a specific object type, you need to use an inline fragment:
请在查询指南的 内联片段 部分了解更多相关信息。
¥Learn more about this in the inline fragments section in the query guide.
联合类型
¥Union types
联合类型与接口有相似之处;然而,它们缺乏在构成类型之间定义任何共享字段的能力。
¥Union types share similarities with interfaces; however, they lack the ability to define any shared fields among the constituent types.
union SearchResult = Human | Droid | Starship
无论我们在结构中返回 SearchResult
类型,我们都可能得到 Human
、Droid
或 Starship
。请注意,联合类型的成员必须是具体的对象类型;你不能从接口或其他联合创建联合类型。
¥Wherever we return a SearchResult
type in our schema, we might get a Human
, a Droid
, or a Starship
. Note that members of a union type need to be concrete object types; you can’t create a union type out of interfaces or other unions.
在这种情况下,如果你查询返回 SearchResult
联合类型的字段,则需要使用内联片段才能查询任何字段:
¥In this case, if you query a field that returns the SearchResult
union type, you need to use an inline fragment to be able to query any fields at all:
__typename
字段解析为 String
,它允许你在客户端上区分不同的数据类型。
¥The __typename
field resolves to a String
which lets you differentiate different data types from each other on the client.
另外,在这种情况下,由于 Human
和 Droid
共享一个公共接口 (Character
),因此你可以在一个位置查询它们的公共字段,而不必在多种类型中重复相同的字段:
¥Also, in this case, since Human
and Droid
share a common interface (Character
), you can query their common fields in one place rather than having to repeat the same fields across multiple types:
{
search(text: "an") {
__typename
... on Character {
name
}
... on Human {
height
}
... on Droid {
primaryFunction
}
... on Starship {
name
length
}
}
}
请注意,name
仍然在 Starship
上指定,因为否则它不会显示在结果中,因为 Starship
不是 Character
!
¥Note that name
is still specified on Starship
because otherwise it wouldn’t show up in the results given that Starship
is not a Character
!
输入类型
¥Input types
到目前为止,我们只讨论了将标量值(例如枚举或字符串)作为参数传递到字段中。但你也可以轻松传递复杂的对象。这在变更的情况下特别有价值,你可能希望传递要创建的整个对象。在 GraphQL 结构语言中,输入类型看起来与常规对象类型完全相同,但使用关键字 input
而不是 type
:
¥So far, we’ve only talked about passing scalar values, like enums or strings, as arguments into a field. But you can also easily pass complex objects. This is particularly valuable in the case of mutations, where you might want to pass in a whole object to be created. In the GraphQL schema language, input types look exactly the same as regular object types, but with the keyword input
instead of type
:
input ReviewInput {
stars: Int!
commentary: String
}
以下是在变更中使用输入对象类型的方法:
¥Here is how you could use the input object type in a mutation:
输入对象类型上的字段本身可以引用输入对象类型,但不能在结构中混合输入和输出类型。输入对象类型的字段也不能有参数。
¥The fields on an input object type can themselves refer to input object types, but you can’t mix input and output types in your schema. Input object types also can’t have arguments on their fields.