GraphQL学习笔记(4)Schema 和类型
1、类型系统Type System
GraphQL 查询语言基本上就是关于选择对象上的字段。
查询示例:
{
hero {
name
appearsIn
}
}
我们以一个特殊的对象 "root" 开始
- 选择其上的
hero字段 - 对于
hero返回的对象,我们选择name和appearsIn字段
因为一个 GraphQL 查询的结构和结果非常相似,因此即便不知道服务器的情况,你也能预测查询会返回什么结果。但是一个关于我们所需要的数据的确切描述依然很有意义,我们能选择什么字段?服务器会返回哪种对象?这些对象下有哪些字段可用?这便是引入 schema 的原因。
每一个 GraphQL 服务都会定义一套类型,用以描述你可能从那个服务查询到的数据。每当查询到来,服务器就会根据 schema 验证并执行查询。
2、类型语言:
GraphQL 服务可以用任何语言编写,因为我们并不依赖于任何特定语言的句法句式(譬如 JavaScript)来与 GraphQL schema 沟通,我们定义了自己的简单语言,称之为 “GraphQL schema language” —— 它和 GraphQL 的查询语言很相似,让我们能够和 GraphQL schema 之间可以无语言差异地沟通。
3、对象类型和字段
一个 GraphQL schema 中的最基本的组件是对象类型,它就表示你可以从服务上获取到什么类型的对象,以及这个对象有什么字段。使用 GraphQL schema language,我们可以这样表示它:
type Character {
name: String!
appearsIn: [Episode]!
}
Character 是一个 GraphQL 对象类型,表示其是一个拥有一些字段的类型。你的 schema 中的大多数类型都会是对象类型。
name和appearsIn是Character类型上的字段。这意味着在一个操作Character类型的 GraphQL 查询中的任何部分,都只能出现name和appearsIn字段。String是内置的标量类型之一 —— 标量类型是解析到单个标量对象的类型,无法在查询中对它进行次级选择。String!表示这个字段是非空的,GraphQL 服务保证当你查询这个字段后总会给你返回一个值。在类型语言里面,我们用一个感叹号来表示这个特性[Episode]!表示一个Episode数组。因为它也是非空的,所以当你查询appearsIn字段的时候,你也总能得到一个数组。数组内的元素可以是空。
4、参数
GraphQL 对象类型上的每一个字段都可能有零个或者多个参数,例如下面的 length 字段:
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
所有的参数必须是具名的,本例中为unit,类型为LengthUnit,对于可选的参数我们可以定义一个默认值,本例中的默认值为METER,因为unit后面没有!
5、查询和变更类型。
你的 schema 中大部分的类型都是普通对象类型,但是一个 schema 内有两个特殊类型:
schema {
query: Query
mutation: Mutation
}
每一个 GraphQL 服务都有一个 query 类型,可能有一个 mutation 类型。这两个类型和常规对象类型无差,但是它们之所以特殊,是因为它们定义了每一个 GraphQL 查询的入口。
这样一个查询:
query {
hero {
name
}
droid(id: "2000") {
name
}
}
那表示这个 GraphQL 服务需要一个 Query 类型,且其上有 hero 和 droid 字段:
type Query {
hero(episode: Episode): Character
droid(id: ID!): Droid
}
Mutation类型同Query类型基本一致,都是定义了入口hero,droid
6、标量类型Scalar Types
标量类型没有任何次级字段了,他们就是最末级
GraphQL 自带一组默认标量类型:
Int:有符号 32 位整数。Float:有符号双精度浮点值。String:UTF‐8 字符序列。Boolean:true或者false。ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。
大部分的 GraphQL 服务实现中,都有自定义标量类型的方式。例如,我们可以定义一个 Date 类型:
scalar Date
然后就取决于我们的实现中如何定义将其序列化、反序列化和验证。例如,你可以指定 Date 类型应该总是被序列化成整型时间戳,而客户端应该知道去要求任何 date 字段都是这个格式。
7、枚举类型
枚举类型是一种特殊的标量,它限制在一个特殊的可选值集合内。
- 验证这个类型的任何参数是可选值的的某一个
- 与类型系统沟通,一个字段总是一个有限值集合的其中一个值
用 GraphQL schema 语言表示的 enum 定义:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
这表示无论我们在 schema 的哪处使用了 Episode,都可以肯定它返回的是 NEWHOPE、EMPIRE 和 JEDI 之一
8、列表和非空
对象类型、标量以及枚举是 GraphQL 中你唯一可以定义的类型种类。
使用!标示非空。示例:
type Character {
name: String!
appearsIn: [Episode]!
}
此处我们使用了一个 String 类型,并通过在类型名后面添加一个感叹号!将其标注为非空。这表示我们的服务器对于这个字段,总是会返回一个非空值。
非空类型修饰符也可以用于定义字段上的参数,如果这个参数上传递了一个空值。
示例:
query DroidById($id: ID!) {
droid(id: $id) {
name
}
}
列表的运作方式也类似:我们也可以使用一个类型修饰符来标记一个类型为 List,表示这个字段会返回这个类型的数组。在 GraphQL schema 语言中,我们通过将类型包在方括号([ 和 ])中的方式来标记列表。
非空可以和列表组合使用。
9、接口
一个接口是一个抽象类型,它包含某些字段,而对象类型必须包含这些字段,才能算实现了这个接口
定义接口:
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
}
注意下面例子的查询会产生错误:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
primaryFunction
}
}
hero 字段返回 Character 类型,取决于 episode 参数,它可能是 Human 或者 Droid 类型。上面的查询中,你只能查询 Character 接口中存在的字段,而其中并不包含 primaryFunction。
可以使用内联片段返回特定的查询。
10、联合类型
联合类型和接口十分相似,但是它并不指定类型之间的任何共同字段。
示例:
union 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",
"height": 1.5
},
{
"name": "TIE Advanced x1",
"length": 9.2
}
]
}
}
11、输入类型
输入对象看上去和常规对象一模一样,除了关键字是 input 而不是 type
input ReviewInput {
stars: Int!
commentary: String
}
你可以像这样在变更(mutation)中使用输入对象类型:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
可以使用输入复杂的对象了:review并不是标量或者枚举类型,而是复杂的对象,这是由于我们定义了输入类型的缘故。
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
结果:
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
输入对象类型的字段当然也不能拥有参数。
浙公网安备 33010602011771号