杨凡 About

GraphQL 纪要

Schema & GraphQL 语句

GraphQL 是一门查询语言,使用它可以描述你需要哪些数据。而 Schema 则是用于声明你能够查询什么样的数据。

举个例子,下面的是 Schema:

type Query {
  hello: String
}

通过以上 Schema 的声明,你可以写下这么一条 GraphQL 查询语句:

query {
  hello
}

Schema

Schema 里主要有两个核心的概念,类型(Type)和字段(Field)。举个例子:

type User {
  name: String
}

上面定义了一个名为 User 的类型,并且这个类型里包含了一个叫做 name 的字段。 注意,字段必须声明类型,可以声明为 Int、String 这类基础类型,也可以声明为自定义的类型。另外,字段是可以包含参数的,这让它看起来有点像函数:

type User {
  name: String
  posts(count: Int): [Post]
}

type Post {
  title: String
}

例如这里 User 类型里的 posts 字段,它接受一个名为 count 的参数并返回一个 Post 类型的列表。

Schema 里的 Query 和 Mutation 类型

在 Schema 里面有两个约定的类型:

type Query {
}

type Mutation {
}

我们假设,任何 GraphQL 语句都其实是在查询 Document 对象的子字段,而 Document 里面有且只有以下两个直接子字段:

type Document {
  query: Query
  mutation: Mutation
}

想要在 GraphQL 语句中查询其它字段,只能把要查询的字段放到 Query 和 Mutation 类型里面。例如我们想添加一个用来查询用户信息的字段,那我们可以在 Query 类型里添加一个 me 字段:

type Query {
  me: User
}

type User {
  name: String
}

那么用户就可以用下面的 GraphQL 语句来查询自己的名称了:

query {
  me {
    name
  }
}

需要注意的是,这里的 query 是字段名,返回的是一个 Query 类型。me 是 Query 类型里的字段,而 name 是 User 类型里的字段。

Query & Mutation 的字段约定

虽然你可以在 Query 和 Mutation 类型里添加任意字段,但是根据语义,应当把不对数据造成修改的字段放在 Query 类型里,而把对数据造成修改的字段放在 Mutation 里。

可以这么理解,查询 Mutation 里的字段实际也是一次「查询」,但是这次查询会对数据造成修改。

以下是一个简单的例子:

type Query {
  me: User
}

type Mutation {
  changeMyName(name: String): User
}

Resolver

Resolver 实际上是一个函数。当我们执行某条 GraphQL 语句时(当然,通常是在 Server 上),会通过执行对应的 Resolver 函数来获取查询的字段的。

拿第一节的例子,Schema 为:

type Query {
  hello: String
}

我们接下来实现一个用来「查询 Query 类型里字段 hello」的 Resolver。例如我们让这个字段返回 “Hello world!” 字符串:

export const resolverMap: IResolvers = {
  Query: {
    hello: {
      resolve: () => ("Hello world!"),
    },
  },
};

这里用了一个双层的 Map(/Object) 来装载所有 Resolver,实际上第一层用来索引类型,第二层用来索引类型中的字段。

而上面键 resolve 对应的函数就是 hello 这个字段的 Resolver 函数。依据 Schema 的声明它需要返回了一个字符串。

我们再来看下如果查询的字段是复杂类型的情况。假设 Schema 为:

type Query {
  me: User
}

type User {
  name: String
  friend: User
}

User 类型里的 friend 字段返回的是一个 User 类型,那么就可能出现下面这样循环嵌套的 GraphQL 语句:

query {
  me {
    name
    friend {
      name
      friend {
        name
      }
    }
  }
}

那我们怎样为这样的语句进行 Resolve 呢?

注意前面说到 Resolver 表。我们可以在表里添加 User 类型,然后为 friend 字段单独添加一个 Resolver:

export const resolverMap: IResolvers = {
  Query: {
    me: {
      resolve: () => ({ name: "Mark" }),
    },
  },
  User: {
    friend: {
      resolve: (parent) =>
        ({ name: (`${parent.name}'s friend`) }),
    },
  },
};

那么,前面的 GraphQL 语句里的三个 name 会分别返回:

Mark
Mark's friend
Mark's friend's friend

注意,这里的 Resolver 函数使用了 parent 参数,它实际上是父字段的返回值。完整的函数签名可以在 官方文档 这里看到。

执行整个 GraphQL 语句的逻辑就是:

留道思考题:如果在 ["Query"]["me"] 这个 Resolver 里返回的是 { name: "Mark", friend: "Tony" } 那最终查询结果会是什么呢?

总结

Schema:用于声明 GraphQL 语句能够查询什么样的数据。

GraphQL 语句:用于描述需要查询哪些数据。

Resolver:用于返回需要的数据。