GraphQL 服务端的库和应用可以用各种语言实现,所以这里的示例脱离语言。
GraphQL 服务端的应用代码的基本实现流程(需要提前安装好 GraphQL 库,各种语言的库参考这里):
每次调用 GraphQL 服务,需要明确指定调用 Schema 中的哪个根类型(默认是 query),然后指定这个根类型下的哪几个字段(每个字段对应一个用户自定义类型),然后指定这些字段中的那些子字段的哪几个。一直到所有的字段都没有子字段为止。
Schema 明确了服务端有哪些字段(用户自定义类型)可以用,每个字段的类型和子字段。每次查询时,服务器就会根据 Schema 验证并执行查询。
定义用户自定义类型:
interface Human {
id: ID!
name: String
age: Int
}
scalar Url
type User implements Human {
id: ID!
name: String
age: Int
is_active: Boolean
friends: [User]!
website: Url
lastStoryPosted: Story
}
type Story {
id: ID!
author: user
comments(after: ID, limit: Int = 5): [Comment]
}
定义根类型(名字随意,和 Schema 中一致即可):
type Query {
user(id: ID): User
viewer: User
stories(after: ID, limit: Int = 10): [Story]!
}
type Mutation {
...// 暂时省略
}
type Subscription{
...// 暂时省略
}
定义 Schema(模型),Schema 文件里有4个关键字:
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
Scalar 是解析到单个标量对象的类型,无法再进行次级选择(不含子字段,不可拆分)。
Scalar 是 GraphQL 查询的叶子节点。下面例子中的 id 字段,就是标量类型。
type Story {
id: ID!
author: user
comments(after: ID, limit: Int = 5): [Comment]
}
GraphQL 自带一组默认标量类型:
"id": "MDU6SXNzdWUyMTc5NTQ0OTc="
。大部分的 GraphQL 服务实现中,都可以自定义标量类型。例如,定义一个 Url 类型:scalar Url
,然后在实现中定义如何将其序列化、反序列化和验证。例如,可以指定 Date 类型应该总是被序列化成整型时间戳,而客户端应该知道任何获取的 date 字段都是这个格式。
Schema 中的最基本的组件是对象,Schema 中的大多数类型都是对象类型。对象由子字段组成。
下面例子中的 Story,就是对象类型。
type Story {
id: ID!
author: user
comments(after: ID, limit: Int = 5): [Comment]
}
详细解释:
接口是一个抽象类型,它包含了某些字段,对象必须包含这些字段,才能实现这个接口。
```
interface Human {
id: ID!
name: String
age: Int
}
type User implements Human {
id: ID!
name: String
age: Int
is_active: Boolean
friends: [User]!
website: Url
lastStoryPosted: Story
}
上面例子中,User 对象含有 Human 接口的所有字段,所以正确实现了 Human 接口。
同一个接口,可以实现出不同的对象,比如 User
和 Viewer
都可以实现 Human
接口。
联合类型必须由对象类型组成,而不能是接口或者其他联合类型。
服务端返回联合类型的字段时,实际上返回的是联合类型中的某一种对象类型,客户端需要用内联片段处理。
和接口十分相似,但是它并不指定类型之间的任何共同字段。
定义联合类型: union SearchResult = Human | Droid | Starship
在我们的schema中,任何返回一个 SearchResult 类型的地方,都可能得到一个 Human、Droid 或者 Starship。注意,联合类型的成员需要是具体对象类型;你不能使用接口或者其他联合类型来创造一个联合类型。
如果查询一个返回联合类型的字段,得使用内联片段才能查询任意字段。
// 请求,通过内联片段取不同对象的字段
query{
search(type:ISSUE,query:"Starkast",first:30) {
edges {
node {
... on Issue {
title
}
... on PullRequest {
number
}
}
}
}
}
在变更(mutation)中会经常用到 Input 输入类型,因为有时候需要传递一整个对象作为新建对象。输入对象定义的关键字是 input。
输入对象类型上的字段本身也可以指代输入对象类型。输入对象类型的字段当然也不能拥有参数。
服务端定义:
input AddReactionInput {
subjectId: ID!
content: String
}
type addReaction {
...
}
type Mutation {
addReaction(params: AddReactionInput): AddReactionPayload
}
客户端请求:
// 请求,字段 addReaction 带了整个 AddReactionInput 类型的对象作为参数
mutation($myVar:AddReactionInput!) {
addReaction(input:$myVar) {
reaction {
content
}
subject {
id
}
}
}
// 查询变量 query variables
{
"myVar": {
"subjectId": "MDU6SXNzdWUyMTc5NTQ0OTc=",
"content": "HOORAY"
}
}
也叫枚举(enum)。枚举类型是一种特殊的标量,为可选值限定了一个集合。
enum Human {
USER
VIEWER
}
上面的例子中,无论在哪里使用了 Human,都可以肯定它返回的是 USER 和 VIEWER 之一。
注意,各种语言实现的 GraphQL 服务,枚举处理方式可能有所差异。
List 列表对应数组,由方括号 []
组成,中间放类型或参数。
非空 可以在类型后面加上感叹号 !
表示。例如:id: ID!
。不能为空就是不能为 null。
非空和列表修饰符可以组合使用。
myField: [String!]
// 表示数组本身可以为空,但是其不能有任何空值(null)成员。用 JSON 举例如下:
myField: null // 有效
myField: [] // 有效
myField: ['a', 'b'] // 有效
myField: ['a', null, 'b'] // 错误
myField: [String]!
// 表示数组本身不能为空,但是可以包含空值成员:
myField: null // 错误
myField: [] // 有效
myField: ['a', 'b'] // 有效
myField: ['a', null, 'b'] // 有效
Non-Null 非空用法:
[]
和 [null]
合法,但 null
非法。对象由子字段组成。对象的每一个字段都可能有参数。所有参数都必须命名。
type Story {
id: ID!
author: user
comments(after: ID, limit: Int = 5): [Comment]
}
上面的例子中,Story 对象有三个字段:id,author,comments。其中 comments 字段有两个参数:after,limit。limit 参数指定了默认值,所以用户可以不传,但是 after 参数必须传。
可选的参数必须定义默认值。
Schema 中的这两个特殊类型和常规对象类型唯一的区别:定义了每一个 GraphQL 查询的入口。
所有的查询,都要定义好后放在 query 对象中。所有的变更,都要定义好后放在 mutation 对象中。
Query 是必不可少的,Mutation 可以不定义。
schema {
query: Query
mutation: Mutation
subscription: Subscription
}