14 graphql学习笔记 - xiaoxin01/Blog GitHub Wiki
https://segmentfault.com/a/1190000014131950
GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
mutation定义的类型用input修饰,如
input AccountInput {
id: ID
Age: Int
Name: String
}
type mutation {
updateAccount(id: ID!, input: AccountInput): Account
}
注:graphql要求至少要有一个query,否则不会列出mutation方法
因为一个 GraphQL 查询的结构和结果非常相似,因此即便不知道服务器的情况,你也能预测查询会返回什么结果。但是一个关于我们所需要的数据的确切描述依然很有意义,我们能选择什么字段?服务器会返回哪种对象?这些对象下有哪些字段可用?这便是引入 schema 的原因。
“GraphQL schema language” —— 它和 GraphQL 的查询语言很相似,让我们能够和 GraphQL schema 之间可以无语言差异地沟通。
type Character {
name: String!
appearsIn: [Episode!]!
}
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
所有参数必须具名传递
schema {
query: Query
mutation: Mutation
}
每一个 GraphQL 服务都有一个 query 类型,可能有一个 mutation 类型。
GraphQL 自带一组默认标量类型:
- Int:有符号 32 位整数。
- Float:有符号双精度浮点值。
- String:UTF‐8 字符序列。
- Boolean:true 或者 false。
- ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化;然而将其定义为 ID 意味着并不需要人类可读型。
我们可以定义一个 Date 类型:
scalar Date
然后就取决于我们的实现中如何定义将其序列化、反序列化和验证。例如,你可以指定 Date 类型应该总是被序列化成整型时间戳,而客户端应该知道去要求任何 date 字段都是这个格式。
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
type Character {
name: String!
myField: [String!]
myField2: [String]!
}
myField: [String!],这表示数组本身可以为空,但是其不能有任何空值成员。用 JSON 举例如下:
myField: null // 有效
myField: [] // 有效
myField: ['a', 'b'] // 有效
myField: ['a', null, 'b'] // 错误
myField2: [String]!,这表示数组本身不能为空,但是其可以包含空值成员:
myField2: null // 错误
myField2: [] // 有效
myField2: ['a', 'b'] // 有效
myField2: ['a', null, 'b'] // 有效
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
}
type Droid implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
primaryFunction: String
}
如果要查询一个只存在于特定对象类型上的字段,你需要使用内联片段:
query HeroForEpisode($ep: Episode!) {
hero(episode: $ep) {
name
... on Droid {
primaryFunction
}
}
}
input ReviewInput {
stars: Int!
commentary: String
}
你可以像这样在变更(mutation)中使用输入对象类型:
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
createReview(episode: $ep, review: $review) {
stars
commentary
}
}
{
"ep": "JEDI",
"review": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
GraphQL 查询中的每个类型的每个字段都由一个 resolver 函数支持,该函数由 GraphQL 服务器开发人员提供。当一个字段被执行时,相应的 resolver 被调用以产生下一个值。
如果字段产生标量值,例如字符串或数字,则执行完成。如果一个字段产生一个对象,则该查询将继续执行该对象对应字段的解析器,直到生成标量值。GraphQL 查询始终以标量值结束。
Human: {
name(obj, args, context, info) {
return obj.name
}
}
在这个例子中,对 name 字段的处理非常的清晰,name 字段对应的解析器被调用的时候,解析器回调函数的 obj 参数是由上层回调函数生成的 new Human 对象。在这个案例中,我们希望 Human 对象会拥有一个 name 属性可以让我们直接读取。
事实上,许多 GraphQL 库可以让你省略这些简单的解析器,假定一个字段没有提供解析器时,那么应该从上层返回对象中读取和返回和这个字段同名的属性。
我们有时候会需要去问 GraphQL Schema 它支持哪些查询。GraphQL 通过内省系统让我们可以做到这点!
{
__schema {
types {
name
}
}
}
{
__schema {
queryType {
name
}
}
}
{
__type(name: "Droid") {
name
kind
}
}
{
__type(name: "Droid") {
name
fields {
name
type {
name
kind
}
}
}
}
https://segmentfault.com/a/1190000014131950
将graphsql的schema定义,从string方式转变为类型定义的方式,虽然代码量会上升,但是多了类型检查,便于维护。
const schema = buildSchema(`
type Account {
id: ID
name: String
age: Int
}
type Query{
account(username: String): Account
}
`)
上述代码转为3部分:
var AccountType = new graphql.GraphQLObjectType({
name: 'Account',
fields: {
id: {type: graphql.GraphQLID},
name: {type: graphql.QraphQLString},
age: {type: graphql.GraphQLInt}
}
})
var queryTYpe = new graphql.GraphQLObjectTpe({
name: 'Query'
fields: {
account: {
type: AccountType,
args: {
id: {type: graphql.GraphQLID}
},
resolve: function(_, {id}) {
return {
id: 1,
name: "test",
age: 18
}
}
}
}
})
var query = new graphql.GraphQLSchema({query: queryType});
如下的query, authors Resolver 先进行 1 次query获取n个authors,然后books Resolver 再进行 n 次query,获取每本书的名字,这就是 n+1 query
{
query {
authors {
name
books {
name
}
}
}
}
主要思想为,减少数据库请求。
dataloader会定义一个时间段,在这个时间段内发起的多个请求,经过处理之后会合并为单个请求,比如 getBookByID(1) 和 getBookByID(2) 会合并为一个 getBooksByIDs(1, 2) 请求
go语言实现:
默认的时间段为16毫秒:
func NewBatchedLoader(batchFn BatchFunc, opts ...Option) *Loader {
loader := &Loader{
batchFn: batchFn,
inputCap: 1000,
wait: 16 * time.Millisecond,
}