此篇文章介紹 graphql 基礎知識,會過於無聊。若是你想快速上手,可使用個人腳手架 shfshanyue/apollo-server-starter。若是你想用它寫一個前端,能夠參考個人 shfshanyue/shici。javascript
本文地址: graphql schema and query前端
咱們先寫一個關於 graphql.js
的 hello, world
示例,而且圍繞它展開對 graphql
的學習java
const {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
} = require('graphql')
// schema,由 web 框架實現時,這部分放在後端裏
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve() {
return 'hello, world'
}
}
}
})
})
// query,由 web 框架實現時,這部分放在前端裏
const query = '{ hello }'
// 查詢,這部分放在服務端裏
graphql(schema, query).then(result => {
// {
// data: { hello: "hello, world" }
// }
console.log(result)
})
複製代碼
由上,也能夠看出 graphql
很關鍵的兩個要素:schema
和 query
。而當咱們開發 web 應用時,schema
將會是服務端的主體,而 query
存在於前端中,相似 REST 中的 API。react
咱們先抽出 schema
的代碼部分,這裏有 GraphQLSchema
,GraphQLObjectType
和 GraphQLString
等諸多 API。git
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve() {
return 'hello, world'
}
}
}
})
})
複製代碼
正如在 React
中使用jsx
簡化了 React.createElement
的寫法。graphql
對於 schema 也有一套它本身的 DSL (Domain Specified Language)
,也更爲簡單,易懂。在代碼中以 graphql
或 gql
做爲文件名後綴,用法以下github
# schama,在後端進行維護
type Query {
hello: String
}
# query,在前端進行管理
{
hello
}
複製代碼
以及查詢結果web
{
hello: 'hello, world'
}
複製代碼
在前端中全部查詢的 gql 每每會經過 graphql/gql 後綴的文件來統一維護,這裏有一份代碼示例: shfshanyue/shici:query.gql面試
你看到這裏,想必有兩個疑問:sql
graphql
表明什麼,以及咱們如何書寫 graphql
DSL
少了一個 resolve
函數,而它又是什麼這裏引入 graphql 中的兩個基本術語,object type
與 field
。它們是組成 graphql 最基本的組件,如同細胞是生物體的基本單位。docker
這裏來一個更復雜的 schema
,以下所示
# schema
schema {
query: Query
}
type Query {
hello: String
todos: [Todo!]!
}
type Todo {
id: ID!
title: String!
}
# query
{
# 只能查詢 Query 下的字段
todos {
id
title
}
hello
}
複製代碼
若是說 graphql 是數據庫的進一步抽象,則 object type
相似於 sql 中的 table
,field
相似於 sql 中的 column
。
那咱們仔細審視以上示例,能從其中獲得一些信息:
type
標註爲 graphql.js
中的類型: GraphQLObjectType
{}
表明一個 query
(查詢),其中由若干字段組成,用以查你所須要的數據Query
是一個特殊的 object type
,表示爲 RootQueryType
,它會放到 schema
中。如同C語言中的 main
函數,能夠理解爲 graphql
的入口查詢。正因如此,它所包含的 field
沒有緊密的內關聯關係。hello
與 todos
是 Query 下的兩個 field
,一切前端的查詢均要從 Query
的 field
查起。如在以上的 query 示例中,只能查詢 todos
與 hello
。Todo
是一個自定義名稱的 object type
,能夠理解爲對應數據庫中的一個 todo 的表。id
與 title
是 Todo 下的 field
,能夠理解他們爲 Todo 的屬性,它們每每由一些基本屬性以及聚合屬性 (count, sum) 組成。[Todo!]!
表明返回結果將是一個 Todo
的數組。[]
表明返回爲數組,!
表明返回不能爲空,[!]
表明數組中的每一項都不能爲空。id: ID!
表明 Todo 的 id 全局惟一title: String!
表明 Todo 的 title 是不爲空的字符串到了這裏,你會發現,對於 graphql schema 的認識還有一些信息還沒有涉及:ID
與 String
,它們被稱做 scalar type
,你能夠理解爲數據類型。 正是由於 scalar,graphql 才成爲強類型查詢語言。
由上所述,Query
爲 graphql
的入口查詢處,咱們能夠而且只能夠查詢 Query
下的任意字段 (field)。所以,他組成了 graphql
最核心的功能: 查找你所須要的任何數據。
# schema
type Query {
hello: String
todos: [Todo!]!
}
// 如下三個 query 通常會在前端進行統一管理
# query 1
{
hello
}
# query 2
{
todos {
id
}
}
# query 3
{
hello
todos {
id
title
}
}
複製代碼
查詢結果以下
{ hello: 'hello, world' }
{ todos: [{ id: 1 }] }
{ hello: 'hello, world', todos: [{ id: 1, title: 'learn react' }] }
複製代碼
在前端咱們根據 Query
組合成各類查詢,而咱們爲了在查詢過程當中方便辨認,能夠爲查詢添加 operationName
:
query HELLO {
hello
}
query TODOS {
todos {
id
}
}
query HELLO_AND_TODOS {
hello
todos {
id
title
}
}
複製代碼
在 graphql
中有一些內置的 scalar
類型,用以表示 graphql 中 field
的數據類型,這也是 graphql 爲強類型語言的基礎。內置類型以下所示
Int
,表明32位有符號型整數Float
String
Boolean
ID
,惟一標識符,通常可視爲數據庫中的主鍵。在 object type
中,通常會把 id 設置爲 ID
類型,依賴它作一些緩存的操做。正由於 scalar
與 !
,來保證了 graphql 的 query 是強類型的。因此當咱們看到以下的 query 時,能夠在前端大膽放心的使用: data.todos.map(todo => todo.title.replace(' ', ''))
。既不用擔憂 data.todos
忽然報錯 Cannot read property 'map' of null
,也不用擔憂 Cannot read property 'title' of null
# schema
type Query {
hello: String
todos: [Todo!]!
}
type Todo {
id: ID!
title: String!
}
# query
{
todos {
id
title
}
}
複製代碼
若是不使用 graphql,你沒法保證響應中數據的類型。你可能須要使用 lodash 來作一些邊界的處理
const data = {
todos: [{
id: 1,
title: 'learn react'
}]
}
// 使用 graphql 後
data.todos.map(todo => todo.title.replace(' ', ''))
// 若是不使用 graphql,你沒法保證返回數據類型。你可能須要使用 lodash 來作一些邊界的處理
_.map(data.todos, todo => _.replace(todo.title, ' ', ''))
複製代碼
當在數據庫中,一個字段除了整型,浮點型等基本類型外,還會有更多並且比較重要和經常使用的數據類型:json
和 datetime
。既然 scalar
用以表示 field
的數據類型,那麼它如何表示 json
與 datetime
或者更多的數據類型呢?
這時,可使用 graphql.js
的API graphql.GraphqlQLScalarType
來自定義 scalar
再回到剛開始的 hello, world
的示例,用 graphql
表示以下
# schema
type Query {
hello: String
}
# query
query HELLO {
hello
}
複製代碼
對以上章節的內容再梳理一遍:
hello
進行查詢,由於該字段在 Query
下HELLO
查詢所獲得的 data.hello
是一個字符串恩?咱們好像把最重要的內容給漏了,hello
中的內容究竟是什麼?!而 resolve
函數就是作這個事的
// 使用 graphql.js 的寫法,把 schema 與 resolve 寫在一塊兒
new GraphQLObjectType({
name: 'RootQueryType',
fields: {
hello: {
type: GraphQLString,
resolve() {
return 'hello, world'
}
}
}
})
// 單獨把 resolve 函數給寫出來
function Query_hello_resolve () {
return 'hello, world'
}
複製代碼
因而咱們再補齊以上內容
# schema
type Query {
hello: String
}
# query
{
hello
}
複製代碼
由此獲得的數據示例
{
hello: 'hello, world'
}
複製代碼
查看一個很典型的 REST 服務端的一段邏輯:抽取用戶ID以及讀取參數(querystring/body)
app.use('/', (ctx, next) {
ctx.user.id = getUserIdByToken(ctx.headers.authorization)
next()
})
app.get('/todos', (ctx) => {
const userId = ctx.user.id
const status = args.status
return db.Todo.findAll({})
})
複製代碼
而在 graphql 中,使用 resolve
函數爲 field
提供數據,而 context,args 都會做爲 resolve
函數的參數
# schema
type Query {
# 如同 REST 通常,能夠攜帶參數,並顯式聲明
todos (status: String): [Todo!]!
}
type Todo {
id: ID!
title: String!
}
# query
{
# 查詢時,在這裏指定參數 (args)
todos (status: "TODO") {
id
title
}
# 同時也能夠指定別名,特別是當有 args 時
done: todos (status: "DONE") {
id
title
}
}
# query with variables
query TODOS ($status: String) {
done: todos (status: $status) {
id
title
}
}
複製代碼
返回數據示例
{
todos: [{ id: 1, title: '松風吹解帶' }],
done: [{ id: 2, title: '山月照彈琴' }],
}
複製代碼
固然,咱們也是經過 Query
以及 Todo 的 resolve 函數來肯定內容,對於如何獲取以上數據以下所示
// Query 的 resolve 函數
const Query = {
todos (obj, args, ctx, info) {
// 從 ctx 中取一些上下文信息,如最多見的 user
const userId = ctx.user.id
const status = args.status
return db.Todo.findAll({})
}
}
// Todo 的 resolve 函數
const Todo = {
title (obj) {
return obj.title
}
}
複製代碼
object type
,如 Todo.title
中 obj
表示 todo
GraphQLResolveInfo
,關於本次查詢的元信息,好比 AST,你能夠對它進行解析從這裏能夠看出來:graphql 的參數都是顯式聲明,而且強類型。這一點比 REST 要好一些
# query with variables
query TODOS ($status: String) {
done: todos (status: $status) {
id
title
}
}
複製代碼
graphql
可以簡化一切的查詢,或者說它是簡化了服務端開發人員 CRUD
中的 Read
。那麼,如何對資源進行修改呢?這裏就提到了 Mutation
。
# 在後端的 schema
schema {
query: Query
mutation: Mutation
}
type Mutation {
addTodo (title: String!): Todo
}
# 在前端的 query
mutation ADD_TODO {
addTodo (title: "學習 React") {
id
title
}
}
複製代碼
// 以上示例返回結果
{
addTodo: { id: 128, title: '學習React' }
}
複製代碼
以上是一個添加 Todo 的例子,從這裏能夠注意到幾點
Mutation
與 Query
一樣屬於特殊的 object type
,一樣,全部關於數據的更改操做都要從 Mutation
中找起,也須要放到 schema
中Mutation
與 Query
分別爲 graphql 的兩大類操做,在前端進行 Mutation
查詢時,須要添加 mutation
字段 (Query
查詢時,在前端添加 query
字段,但這不是必選的)type Mutation {
addTodo (title: String!): Todo
}
mutation ADD_TODO {
addTodo (title: "學習 React") {
id
title
}
}
複製代碼
以上是一個關於添加 Todo 的 mutation,在咱們添加一個 Todo 時,它僅有一個屬性: title。若是它擁有更多個屬性呢?這時,可使用 input type
,把某一資源的全部屬性聚合起來。而且配合 variables
一塊兒使用傳遞數據
input InputTodo {
title: String!
}
type Mutation {
addTodo (todo: InputTodo!): Todo
}
mutation ADD_TODO ($todo: InputTodo!) {
addTodo (todo: $todo) {
id
title
}
}
複製代碼
// $todo 的值,在前端獲取數據時,使用 variables 傳入
{
title: '學習 React'
}
複製代碼
我是山月,我會按期分享全棧文章在我的公衆號中。若是你對全棧面試,前端工程化,graphql,devops,我的服務器運維以及微服務感興趣的話,能夠關注我。若是想進羣交流,能夠添加我微信 shanyue94,備註加羣。