GraphQL 是一种用于 API 的查询语言和运行时环境,由 Facebook 开发并于2015年公开发布。它旨在提供一个更高效、强大且灵活的替代方案, 用于客户端和服务器之间的数据交互。与传统的 RESTful API 不同,精确的数据查询,允许仅获取所需的数据等都是 GraphQL 的优势。 GraphQl 和 RESTful 没有绝对的孰优孰劣,你应该在合适的项目里选择最合适的 API 解决方案。
GraphQL 的优势主要在于精确的数据查询,允许仅获取所需的数据,从而节省带宽和资源,这将通过一个例子来理解。
GraphQL 服务是通过定义类型和字段,为每种类型上的每个字段提供函数来创建的。查询用户的 GraphQL 服务可能长这样:
type Query {
me: User
}
type User {
id: ID!
name: String!
}
GraphQL 服务在运行时主要由两部分组成,Query Language 和 Engine,后端定义了类型和字段,客户端一样需要通过 HTTP 或者 WebSocket 发送请求, 然后 GraphQL Engine 会根据请求的查询和类型定义来返回数据。
例如查询 “me”:
{
me {
name
}
}
后端返回:
{
"me": {
"name": "Luke Skywalker"
}
}
查询与返回结果具有完全相同的字段,这便是 GraphQL 的优势之一,你总能获取你期望的结果,而后端确切的知道客户端请求的字段。
在 GraphQl 查询中,可以通过给字段添加参数来完美替代多次 API 获取请求。 例如,假设我们想要获取 Luke Skywalker 的朋友,我们可以使用如下查询:
{
people(name: "Luke Skywalker") {
id
friends(first: 1) {
name
}
}
}
返回结果
{
"people": {
"id": "1",
"friends": [
{
"name": "Han Solo"
}
]
}
}
在 schema 中有两个特殊类型: query 和 mutation。每个 GraphQL 服务都有一个 query 类型,可能存在一个 mutation 类型。 它们定义了每个查询的入口。
GraphQL 自带的默认标量类型有:
自定义标量:可以通过定义标量,序列化和反序列化来自定义类型。
scalar Date
可以通过枚举将值限制在可选值集合内。
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
这意味着当我们在 schema 任意位置使用 Episode 时,它的返回只能是 NEWHOPE、EMPIRE 或 JEDI 之一。
列表和非空类型,可以通过给类型添加额外的类型修饰符来影响值的验证。
type Character {
name: String!
appearsIn: [Episode]!
}
其中 !
表示非空,意味着对于这个字段总会返回一个非空值,如果返回了空值,GraphQL 会返回一个错误。[]
类似,表示一个类型为 List。
可以通过相互组合来达到某种效果:field: [String!]
表示 field 是非空数组, field: [String]!
表示 field 一定会返回数组,但可能是空数组。
接口是一个抽象类型,它包含一些字段,而对象类型必须包含这些字段才算实现了接口,并且可以包含额外的字段。
interface Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
}
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
# 必须实现以上字段
starships: [Starship]
totalCredits: Int
}
联合类型和接口十分相似,但他并不会要求类型之间必须共同实现字段。
union SearchResult = Human | Droid | Starship
如果使用联合类型,查询时需要使用内联片段来指定返回类型。
{
search(text: "an") {
__typename
... on Human {
name
height
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
{
"data": {
"search": [
{
"__typename": "Human",
"name": "Han Solo",
"height": 1.8
},
{
"__typename": "Human",
"name": "Leia Organa",
"height": 1.5
},
{
"__typename": "Starship",
"name": "TIE Advanced x1",
"length": 9.2
}
]
}
}
在进行变更操作的时候(mutation),有时候参数是一个复杂对象,这时候就需要使用到关键字 input 创建输入类型, 它和常规对象的区别只在于使用关键字为 input 而非 type
input ReviewInput {
starts: Int!
commentary: String
}
在字段后面添加 @deprecated 可以标记字段为弃用,GraphQL 会返回一个警告,但不影响继续使用这个字段,这在接口迭代时非常有用。 并且可以添加 reason 字段来描述弃用原因。
type Character {
name: String @deprecated(reason: "Use `hero` instead")
}
type Query {
# 获取账户的前二十条草稿邮件
# 查询字段应该直接使用名词,不要使用动词 + 名词(例如:getAccounts / getAccountById 等)
account(id: ID!) {
drafts(first: Int): Email
unreadEmailCount: Int
}
}
type Email {
id: ID!
subject: String!
}
可以通过一个例子来快速掌握:
{
hero {
name
# first 限制返回条目,而after 是游标(游标一般不可读)用于标识偏移
friendsConnection(first: 2, after: "Y3Vyc29yMQ==") {
totalCount
# 引入新的间接层 edges ,在 friends 的基础上返回当前游标
edges {
node {
name
}
cursor
}
pageInfo {
endCursor
hasNextPage
startCursor
hasPreviousPage
}
}
}
}
你可以在这里找到参考资料