在此页面中,您将了解关于在类型系统的全部须要了解的内容,以及它如何描述能够查询哪些数据。因为GraphQL能够与任何后端框架或编程语言一块儿使用,咱们将远离具体的具体细节,只讨论概念。编程
类型系统后端
若是您之前看到过一种查询,您知道在查询语言基本上是关于选择对象上的字段。例如,在下面的查询中:数组
{
hero {
name
appearsIn
}
}
结果缓存
{ "data": { "hero": { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] } } }
因为一种查询的形状与结果很是匹配,因此您能够在不知道有关服务器的状况的状况下预测查询返回的内容。可是,有一个咱们能够要求的数据的精确描述是有用的-咱们能够选择哪些字段?他们会返回什么样的物体?在这些子对象上有哪些字段可用?这就是模式的用武之地。服务器
每一个GraphQL服务定义了一组类型,它们彻底描述了您能够在该服务上查询的可能数据集。而后,当查询进入时,它们将被验证并针对该模式执行。app
GraphQL服务能够用任何语言编写。因为咱们不能依赖特定的编程语言语法(好比JavaScript)来讨论GraphQL模式,咱们将定义本身的简单语言。咱们将使用“GraphQL模式语言”-它相似于查询语言,容许咱们以一种GraphQL的语言来讨论基于语言的模式。框架
Graphql模式最基本的组件是对象类型,它们只表明您能够从服务中获取的对象类型,以及它拥有的字段。在GraphQL 模式语言中,咱们能够这样表示:编程语言
type Character { name: String! appearsIn: [Episode]! }
这门语言很容易读,可是让咱们来复习一下,这样咱们就能够有一个共同的词汇:ide
l Character
是一个GraphQL对象类型,它包含了一些字段。模式中的大多数类型都是对象类型。函数
l name
和appearsIn
字段都属于Character
类型。这意味着name
和appearsIn
是惟一能够出如今运行于Character
的GraphQL查询中的任何部分的字段。
l String
是内置标量类型之一-它们是解析为单个scalar types的类型,而且在查询中不能有子选择。稍后咱们将更多地讨论scalar types 。
l String! 表示字段是不可空的,这意味着在查询此字段时,GraphQL服务承诺始终给出一个值。在类型语言中,咱们将用感叹号表示那些。
l [Episode]! 表示一个Episode对象集合的数组。因为它也是不能够为空的,因此当您查询在字段时,您老是能够获得一个数组(包含零个或多个项)。
如今您知道了一种对象类型是什么样子的,以及如何读取在类型语言的基本内容。
GraphQL对象中的每个字段均可以有一个或者多个参数,以length字段为例:
type Starship { id: ID! name: String! length (unit: LengthUnit = METER): Float }
全部参数都被命名,与JavaScript和Python这样的语言不一样的是,函数中的全部参数都是按名称传递的。在这个例子中,length字段定义了一个参数unit。
参数能够是必需的,也能够是可选的。当参数是可选的时,咱们能够定义一个默认值-若是单元参数不传递,它将默认设置为METER。
模式中的大多数类型都只是普通对象类型,但在模式中有两种特殊类型:
schema {
query: Query
mutation: Mutation
}
每一个GraphQL服务都有一个query
类型,而且可能有或可能没有mutation
类型。这些类型与常规对象类型相同,但它们是特殊的,由于它们定义了每一个GraphQL查询的入口点。所以,若是您看到一个查询,它看起来像:
query { hero { name } droid (id: "2000") { name } }
结果
{ "data": { "hero": { "name": "R2-D2" }, "droid": { "name": "C-3PO" } } }
这意味着在服务须要具备hero和droid字段的查询类型:
type Query { hero (episode: Episode): Character droid (id: ID!): Droid }
Mutations 的工做方式相似-在Mutation
类型上定义字段,这些字段能够做为根mutation 字段,您能够在查询中调用这些字段。
重要的是要记住,除了做为模式的“entry point" 的特殊状态以外,Query
和Mutation
类型与任何其余GraphQL对象类型同样,它们的字段的工做方式彻底相同。
一种对象类型有一个名称和字段,可是在某个时候这些字段必须解析为一些具体的数据。这就是标量类型的来源:它们表明查询的叶子。
在下面的查询中,name和appearsIn将解析为标量类型:
{
hero {
name
appearsIn
}
}
结果
{ "data": { "hero": { "name": "R2-D2", "appearsIn": [ "NEWHOPE", "EMPIRE", "JEDI" ] } } }
咱们知道这一点,由于这些字段没有子字段-它们是查询的叶子。
GraphQL附带了一组默认标量类型,以下:
l int:一个有符号的32位整数。
l 浮点:一个有符号的双精度浮点值 。
l 字符串:一个UTF字符序列。
l 布尔:true或false。
l id:id标量类型表示惟一标识符,一般用于对对象进行从新定位或做为缓存的键。id类型以与字符串相同的方式序列化。
在大多数GraphQL服务实现中,还有一种指定自定义标量类型的方法。例如,咱们能够定义日期类型:
scalar Date
而后由咱们的实现来定义应该如何序列化该类型,反序列化并验证。例如,能够指定始终将日期类型序列化为整数时间戳,而且你的客户端知道指望要格式成日期字段。
枚举类型是一种特殊的标量,它被限制在一组特定的容许值。这使您可以:
下面是一个定义在模式语言中的样子:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
这意味着不管咱们在咱们的模式中使用类型Episode
,咱们指望它彻底是一个NEWHOPE
,EMPIRE
,或者 JEDI。
对象类型、标量和枚举是能够在GraphQL中定义的惟一类型。可是,当您在模式的其余部分使用类型时,或者在查询变量声明中,能够应用影响这些值验证的附加类型修饰符。
让咱们看一个例子:
type Character { name: String! appearsIn: [Episode]! }
在这里,咱们使用一个字符串类型并将它标记为非空,添加一个感叹号,!类型名称以后。这意味着咱们的服务器老是指望返回这个字段的非空值,若是它最终获得一个空值,实际上会触发一个GraphQL执行错误,那么让客户端知道出了问题。
在为字段定义参数时,也可使用非空类型修饰符,这将致使在服务器返回一个验证错误,若是一个空值做为该参数传递,不管是在字符串仍是在变量中。
query DroidById($id: ID!) {
droid(id: $id) {
name
}
}
参数
{ "id": null }
结果
{ "errors": [ { "message": "Variable \"$id\" of required type \"ID!\" was not provided.", "locations": [ { "line": 1, "column": 17 } ] } ] }
列表的工做方式相似:咱们可使用类型修饰符将类型标记为列表,指示此字段将返回该类型的数组。在模式语言中,这是经过将类型包装在方括号中表示的,[和]。它对参数的做用是相同的,在这个参数中,验证步骤将指望这个值的数组。
能够将非空和列表修饰符组合在一块儿。例如,您能够有一个非空字符串列表:
myField: [String!]
这意味着列表自己能够是null,但它不能有任何空成员。例如,在JSON:
myField: null // valid myField: [] // valid myField: ['a', 'b'] // valid myField: ['a', null, 'b'] // error
如今,假设咱们定义了一个非空的字符串列表:
myField: [String]!
这意味着列表自己不能为空,但能够包含空值:
myField: null // error myField: [] // valid myField: ['a', 'b'] // valid myField: ['a', null, 'b'] // valid
您能够任意嵌套任意数量的非空和列表修饰符,根据您的须要。
与许多类型系统同样,GraphQL支持接口。接口是一个抽象类型,它包含一个特定的字段集合,一个类型必须包含的实现接口的字段。
例如,你能够有一个接口Character
,在星球大战三部曲表明任何一个角色:
interface Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! }
这意味着任何实现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 这是区别于接口Character中的字段。
当您想要返回一个对象或一组对象时,接口是有用的,可是这些对象可能有几种不一样的类型。
例如,注意下面的查询会产生一个错误:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
primaryFunction
}
}
参数
{ "ep": "JEDI" }
结果
{ "errors": [ { "message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?", "locations": [ { "line": 4, "column": 5 } ] } ] }
在hero
字段返回类型Character,这意味着它多是一个Human
或一个Droid
,取决于episode
的参数。在上面的查询中,您只能请求在Character接口上存在的字段,不包括primaryFunction。
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
}
}
参数
{ "ep": "JEDI" }
结果
{ "data": { "hero": { "name": "R2-D2", "primaryFunction": "Astromech" } } }
在查询指南的行内片断部分中了解有关此内容的更多信息。
联和类型与接口很是类似,但它们不能指定类型之间的任何公共字段。
union SearchResult = Human | Droid | Starship
返回一个SearchResult类型,咱们均可以获得一个Human,一个Droid,或者一个Starship。联合类型的成员须要是具体的对象类型;您不能从接口或其余联合建立一个联合类型。
在这种状况下,若是查询返回在SearchResult
联合类型的字段,则须要使用条件片断才能查询任何字段:
{ search(text: "an") { ... on Human { name height } ... on Droid { name primaryFunction } ... on Starship { name length } } }
结果
{ "data": { "search": [ { "name": "Han Solo", "height": 1.8 }, { "name": "Leia Organa", "primaryFunction": "Astromech" }, { "name": "TIE Advanced x1", "length": 9.2 } ] } }
到目前为止,咱们只讨论了将标量值(如枚举或字符串)做为参数传递到字段中。可是,您也能够轻松地传递复杂的对象。在发生mutations的状况下,这一点特别有用,您可能但愿传入要建立的整个对象。
在 GraphQL schema 语言中。输入类型与常规对象类型彻底相同,但使用input
而不是type:
input ReviewInput { stars: Int! commentary: String }
下面是如何在一种修改中使用input对象类型:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
参数
{ "ep": "JEDI", "review": { "stars": 5, "commentary": "This is a great movie!" } }
结果
{ "data": { "createReview": { "stars": 5, "commentary": "This is a great movie!" } } }
输入对象类型上的字段能够本身引用输入对象类型,但不能在模式中混合输入和输出类型。输入对象类型也不能在其字段上有参数。