---
GraphQL [ɡr?f]
---
### GraphQL是什么? (graph n:圖; 圖表; 曲線圖) 美:[ɡr?f]
>關(guān)于GraphQL是什么,網(wǎng)上一搜一大堆。根據(jù)官網(wǎng)的解釋就是一種用于 API 的查詢語言。
>一看到用于API的查詢語言明刷,我也是一臉懵逼的奸晴。博主你在開玩笑吧?你的翻譯水平不過關(guān)贼涩?API還能查嗎茅逮?API不是后端寫好璃赡,前端調(diào)用的嗎判哥?
的確可以,這就是GraphQL強(qiáng)大的地方碉考。
### 為什么要使用GraphQL?
>在實(shí)際工作中往往會有這種情景出現(xiàn):比如說我需要展示一個商品的列表塌计,可接口卻會把商品的描述,更新時間侯谁,創(chuàng)建者等各種各樣的 (無用的) 信息都一同返回锌仅。
問了后端,原因大概如下:
>* 原來是為了兼容PC端和移動端用同一套接口
>* 或者在整個頁面墙贱,這里需要顯示商品的標(biāo)題热芹,可是別的地方需要顯示商品明細(xì)啊,避免多次請求我就全部返回咯
>* 或者是因?yàn)橛袝r候項(xiàng)目經(jīng)理想要顯示“標(biāo)題+更新時間”惨撇,有時候想要點(diǎn)擊標(biāo)題展開商品明細(xì)等等需求伊脓,所以把商品相關(guān)的信息都一同返回
簡單說就是:
>* 兼容多平臺導(dǎo)致字段冗余
>* 一個頁面需要多次調(diào)用 API 聚合數(shù)據(jù)
>* 需求經(jīng)常改動導(dǎo)致接口很難為單一接口精簡邏輯
有同學(xué)可能會說那也不一定要用GraphQL啊,比方說第一個問題魁衙,不同平臺不同接口不就好了嘛
```
http://api.xxx.com/web/getGameInfo/:gameID
http://api.xxx.com/app/getGameInfo/:gameID
http://api.xxx.com/mobile/getGameInfo/:gameID
```
或者加個參數(shù)也行
```
http://api.xxx.com/getGameInfo/:gameID?platfrom=web
```
這樣處理的確可以解決問題报腔,但是無疑加大了后端的處理邏輯。你真的不怕后端程序員打你剖淀?
這個時候我們會想纯蛾,接口能不能不寫死,把靜態(tài)變成動態(tài)纵隔?
### GraphQL嘗嘗鮮——(GraphQL簡單例子)
下面是用GraphQL.js和express-graphql搭建一個的普通GraphQL查詢(query)的例子翻诉,包括講解GraphQL的部分類型和參數(shù),已經(jīng)掌握了的同學(xué)可以跳過捌刮。
> 1.先跑個hello world
>* 1.新建一個graphql文件夾米丘,然后在該目錄下打開終端,執(zhí)行npm init --y初始化一個packjson文件糊啡。
>* 2.安裝依賴包:npm install --save -D express express-graphql graphql
>* 3.新建schema.js文件,填上下面的代碼
>1.hello world
>* 先簡單講解一下代碼:
```typescript
const queryObj = new GraphQLObjectType({
? ? name: 'myFirstQuery',
? ? description: 'a hello world demo',
? ? fields: {}
});
```
GraphQLObjectType是GraphQL.js定義的對象類型吁津,包括name、description 和fields三個屬性,其中name和description 是非必填的徊件。fields是解析函數(shù)卖擅,在這里可以理解為查詢方法
```typescript
hello: {
? ? name: 'a hello world query',
? ? description: 'a hello world demo',
? ? type: GraphQLString,
? ? args: {
? ? ? ? name: {? // 這里定義參數(shù),包括參數(shù)類型和默認(rèn)值
? ? ? ? ? ? type: GraphQLString,
? ? ? ? ? ? defaultValue: 'Brian'
? ? ? ? }
? ? },
? ? resolve(parentValue, args, request) { // 這里演示如何獲取參數(shù)典尾,以及處理
? ? ? ? return 'hello world ' + args.name + '!';
? ? }
}
```
對于每個fields役拴,又有name,description钾埂,type河闰,resolve參數(shù)科平,這里的type可以理解為hello方法返回的數(shù)據(jù)類型,resolve就是具體的處理方法姜性。
說到這里有些同學(xué)可能還不滿足瞪慧,如果我想每次查詢都想帶上一個參數(shù)該怎么辦,如果我想查詢結(jié)果有多條數(shù)據(jù)又怎么處理部念?
schema.js文件弃酌,來一個加強(qiáng)版的查詢(當(dāng)然,你可以整理一下代碼儡炼,我這樣寫是為了方便閱讀)
```typescript
const {
? ? ? GraphQLSchema,
? ? ? GraphQLObjectType,
? ? ? GraphQLString,
? ? ? GraphQLInt,
? ? ? GraphQLBoolean
? ? } = require('graphql');
const queryObj = new GraphQLObjectType({
? ? name: 'myFirstQuery',
? ? description: 'a hello world demo',
? ? fields: {
? ? ? ? hello: {
? ? ? ? ? ? name: 'a hello world query',
? ? ? ? ? ? description: 'a hello world demo',
? ? ? ? ? ? type: GraphQLString,
? ? ? ? ? ? args: {
? ? ? ? ? ? ? ? name: {? // 這里定義參數(shù)妓湘,包括參數(shù)類型和默認(rèn)值
? ? ? ? ? ? ? ? ? ? type: GraphQLString,
? ? ? ? ? ? ? ? ? ? defaultValue: 'Brian'
? ? ? ? ? ? ? ? }
? ? ? ? ? ? },
? ? ? ? ? ? resolve(parentValue, args, request) { // 這里演示如何獲取參數(shù),以及處理
? ? ? ? ? ? ? ? return 'hello world ' + args.name + '!';
? ? ? ? ? ? }
? ? ? ? },
? ? ? ? person: {
? ? ? ? ? ? name: 'personQuery',
? ? ? ? ? ? description: 'query a person',
? ? ? ? ? ? type: new GraphQLObjectType({ // 這里定義查詢結(jié)果包含name,age,sex三個字段乌询,并且都是不同的類型榜贴。
? ? ? ? ? ? ? ? name: 'person',
? ? ? ? ? ? ? ? fields: {
? ? ? ? ? ? ? ? ? name: {
? ? ? ? ? ? ? ? ? ? type: GraphQLString
? ? ? ? ? ? ? ? ? },
? ? ? ? ? ? ? ? ? age: {
? ? ? ? ? ? ? ? ? ? type: GraphQLInt
? ? ? ? ? ? ? ? ? },
? ? ? ? ? ? ? ? ? sex: {
? ? ? ? ? ? ? ? ? ? type: GraphQLBoolean
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }),
? ? ? ? ? ? args: {
? ? ? ? ? ? ? ? name: {
? ? ? ? ? ? ? ? ? ? type: GraphQLString,
? ? ? ? ? ? ? ? ? ? defaultValue: 'Charming'
? ? ? ? ? ? ? ? }
? ? ? ? ? ? },
? ? ? ? ? ? resolve(parentValue, args, request) {
? ? ? ? ? ? ? ? return {
? ? ? ? ? ? ? ? ? ? name: args.name,
? ? ? ? ? ? ? ? ? ? age: args.name.length,
? ? ? ? ? ? ? ? ? ? sex: Math.random() > 0.5
? ? ? ? ? ? ? ? };
? ? ? ? ? ? }
? ? ? ? }
? ? }
});
module.exports = new GraphQLSchema({
? query: queryObj
});
```
打開http://localhost:8000,在左側(cè)輸入
```typescript
{
? hello(name:"charming"),
? person(name:"charming"){
? ? name,
? ? sex,
? ? age
? }
}
```
右側(cè)就會顯示出:

你可以在左側(cè)僅輸入person方法的sex和age兩個字段楣责,這樣就會只返回sex和age的信息竣灌。動手試一試吧!
```typescript
{
? person(name:"charming"){
? ? sex,
? ? age
? }
}
```
當(dāng)然,結(jié)果的順序也是按照你輸入的順序排序的秆麸。
定制化的數(shù)據(jù)初嘹,完全根據(jù)你查什么返回什么結(jié)果。這就是GraphQL被稱作API查詢語言的原因沮趣。
> GraphQL是應(yīng)用層查詢語言屯烦,它有自己的數(shù)據(jù)類型,用來描述數(shù)據(jù)房铭,比如標(biāo)量類型驻龟、集合類型、Object類型缸匪、枚舉等翁狐,甚至有接口類型。而GraphQL服務(wù)端不依賴任何語言凌蔬,我只是從概念上來了解其數(shù)據(jù)類型露懒。
### 標(biāo)量類型(Scala)
> 標(biāo)量類型是不能包含子字段,主要有如下類型:
> * Int: 有符號的32位整型
> * Float: 有符號的雙精度浮點(diǎn)型
> * String: UTF‐8字符序列
> * Boolean: 布爾型
> * ID: 常用于獲取數(shù)據(jù)的唯一標(biāo)志砂心,或緩存的鍵值懈词,它也會被序列化為String,但可讀性差辩诞。
> 以上的類型是GraphQL規(guī)范的基本類型坎弯,每個規(guī)范的實(shí)現(xiàn)也可以有自定以標(biāo)量,如 scala Date ,但它必須能夠>序列和反序列化抠忘,至于如何定于取決于具體的規(guī)范實(shí)現(xiàn)撩炊。
1、枚舉(Enum)
枚舉類型是標(biāo)量類型的變體褐桌,不僅適用于可驗(yàn)證性衰抑,還提高了維護(hù)性,它同樣被序列化為String荧嵌。定義形如
```typescript
enum Unit {
? MM
? mm
}
```
MM代表米做單位呛踊,mm代碼毫米做單位。
2啦撮、對象(Ojbect)
> 組成Schema最常用的是對象類型,它包含各種字段谭网,定義如:
```typescript
type User{
name: String
sex: String
intro: String
}
```
以上定義了一個User對象,包含name(名字)赃春、sex(性別)愉择、intro(介紹)屬性字段,而這些屬性字段都是標(biāo)量String類型织中,當(dāng)然屬性也可以是對象類型锥涕。
3、集合(List)
> GraphQL規(guī)范中的集合只有List一種狭吼,它是有序的用 [] 表示层坠,如
```typescript
type User{
? name: String
? sex: String
? intro: String
? skills: [String]
}
```
skills(技能)就是一個list集合,其實(shí)更像是一個數(shù)組刁笙。
4破花、非空/Null(Non-Null)
> 在類型后使用 ! 聲明 非空/Null,如
```typescript
type Query {
? user(id:Int!):User
}
type User{
? name: String!
? sex: String
? intro: String
? skills: [String]!
}
```
> 在類型后使用 ! 聲明 非空/Null疲吸,如? (:8001)
```typescript
query{
? user {
? ? name
? ? sex
? ? intro
? }
}
```
> 則會反饋異常?
```typescript
{
? "errors": [
? ? {
? ? ? "message": "Field \"user\" argument \"id\" of type \"Int!\" is required but not provided.",
? ? ? "locations": [
? ? ? ? {
? ? ? ? ? "line": 2,
? ? ? ? ? "column": 3
? ? ? ? }
? ? ? ]
? ? }
? ]
}
```
> 而實(shí)體類型User的name為非null座每,一但服務(wù)端返回的數(shù)據(jù)name為null,則反饋為
```typescript
{
? "data": {
? ? "user": null
? }
}
```
> 然而需要注意區(qū)別 skills: [String]! 與 skills: [String!] 摘悴,前者是集合不能為null峭梳,后者是集合元素不能為null。
5蹂喻、模式(Schema)
> GraphQL的Schema用于生成文檔葱椭、格式定義與校驗(yàn)等,除了自定義的類型如上文中的User叉橱,還有兩個特殊的類型Query(查詢)和Mutation(維護(hù))。如增查改刪(CRUD)者蠕,增改刪屬于后者窃祝,查屬于前者。 (:8001)
```typescript
schema {
? query: Query
? mutation: Mutation
}
```
> 一個Schema可以沒有mutaion踱侣,但必須有query粪小。
6大磺、查詢(Query)
> Query目的是獲取數(shù)據(jù)。格式
7探膊、維護(hù)(Mutation)
> Mutataion是用來維護(hù)數(shù)據(jù)的杠愧,格式和查詢類似
8、參數(shù)(Argument)
> 客戶端的查詢語句中對象和字段都可以傳入?yún)?shù)逞壁,方便精確控制服務(wù)返回結(jié)果流济,查詢語句如 (:8002)
```typescript
{
? user(id:1) {
? ? name
? ? sex
? ? intro
? ? skills
? ? stature(unit:MM)
? }
}
```
> 服務(wù)返回?cái)?shù)據(jù)
```typescript
{
? "data": {
? ? "user": {
? ? ? "name": "James",
? ? ? "sex": "男",
? ? ? "intro": "zhaiqianfeng的英文名",
? ? ? "skills": [
? ? ? ? "Linux",
? ? ? ? "Java",
? ? ? ? "nodeJs",
? ? ? ? "前端"
? ? ? ],
? ? ? "stature": 1.8
? ? }
? }
}
```
> 服務(wù)端是靠resolver來解析實(shí)現(xiàn)的,后文描述腌闯。
9绳瘟、 指令(Directive)
>客戶端可以使用兩種指令skip和include,可以用與字段(field)姿骏、片段(fragment)糖声,格式和含義如下:
>* field/fragment @skip(if: $isTrue) 當(dāng)$isTrue為真(true)時不查詢field或不使用fragment;
>* field/fragment @include(if: $isTrue) 當(dāng)$isTrue為真(true)時查詢field或使用fragment分瘦;
> 而參數(shù)變量是通過query或mutation傳遞的蘸泻;變量形如$withName:Boolean!,以$開頭嘲玫,以類型結(jié)尾悦施,類型必須是標(biāo)量(scalar)、枚舉(enum)或輸入類型(input)趁冈。如query為 (:8005)
```typescript
query(
? $noWithDog:Boolean!,
? $withName:Boolean!,
? $withFish:Boolean!
){
? animals{
? ? name @include(if:$withName)
? ? ... dogQuery @skip(if:$noWithDog)
? ? ... on Fish @include(if:$withFish){
? ? ? tailColor
? ? }
? }
}
fragment dogQuery on Dog{
? legs
}
```
在query定義中分別定義了$noWithDog(是否帶著狗狗)歼争、$withName(是否帶著Name)、$withFish(是否帶著魚兒)變量渗勘,傳入的參數(shù)變量為
```typescript
{
? "noWithDog": true,
? "withName": true,
? "withFish": true
}
```
查詢的結(jié)果如
```typescript
{
? "data": {
? ? "animals": [
? ? ? {
? ? ? ? "name": "dog"
? ? ? },
? ? ? {
? ? ? ? "name": "fish",
? ? ? ? "tailColor": "red"
? ? ? }
? ? ]
? }
}
```
10沐绒、輸入(Input)
>上面的參數(shù)(Argument)我們使用是標(biāo)量,但當(dāng)使用Mutation來更新數(shù)據(jù)時旺坠,你可能更喜歡傳入一個復(fù)雜的實(shí)體Object乔遮,GraphQL使用input關(guān)鍵字來到定義輸入類型,不直接用Object是為了避開循環(huán)引用取刃、接口或聯(lián)合等一些不可控的麻煩蹋肮,特別是input類型不能像Object那樣帶參數(shù)的。如
```typescript
input UserInput {
? name: String!
? sex: String
? intro: String
? skills: [String]!
}
```
> 定義了一個輸入類型UserInput璧疗,字段有name坯辩、sex、intro和skills.
11崩侠、接口(Interface)
> 類似其他語言漆魔,GraphQL也有接口的概念,方便查詢時返回統(tǒng)一類型,接口是抽象的數(shù)據(jù)類型改抡,因此只有接口的實(shí)現(xiàn)才有意義矢炼,如
```typescript
interface Animal{
name: String!
}
type Dog implements Animal{
name: String!
legs: Int!
}
type Fish implements Animal{
name: String!
tailColor: String!
}
```
> 上面的接口Animal有兩個實(shí)現(xiàn):Dog和Fish,它們都有公共字段name阿纤。服務(wù)端的查詢Schema可以是類似如下的定義
```typescript
type Query {
? animals:[Animal]!
}
```
> 而客戶端的查詢需要使用下面的Fragment句灌,稍后展示。
12欠拾、聯(lián)合(Union)
> 聯(lián)合查詢胰锌,類似接口式的組合,但不要求有公共字段清蚀,使用union關(guān)鍵字來定義匕荸,如
```typescript
type Dog{
chinaName: String!
legs: Int!
}
type Fish{
englishName: String!
tailColor: String!
}
union Animal = Dog | Fish
```
> 則Animal可以是Dog也可以是Fish,服務(wù)端定義查詢可以和上面的接口相同枷邪,客戶端查詢也需要使用Fragment榛搔,稍后展示。
13东揣、 片段(Fragment)
> Fragment分為內(nèi)聯(lián)和外聯(lián)践惑,兩種都是用于選擇主體,而后者還可以共用代碼嘶卧。如上面的接口示例尔觉,Dog和Fish都是接口Animal的實(shí)現(xiàn),有接口的公共字段name芥吟,簡單的內(nèi)聯(lián) (:8003)
```typescript
{
? animals{
? ? name
? ? ... on Dog{
? ? ? legs
? ? }
? ? ... on Fish{
? ? ? tailColor
? ? }
? }
? animalSearch(text:"dog"){
? ? name
? ? ... on Dog{
? ? ? legs
? ? }
? ? ... on Fish{
? ? ? tailColor
? ? }
? }
}
```
> 如果是Dog則查詢legs字段侦铜,如果是Fish則查詢tailColor字段,這種內(nèi)聯(lián)如果單個查詢則比較方便钟鸵,但多個查詢則使用外聯(lián)更簡潔钉稍,如下
```typescript
{
? animals{
? ? ... animalName
? ? ... dogLegs
? ? ... fishTail
? }
? animalSearch(text:"dog"){
? ? ... animalName
? ? ... dogLegs
? ? ... fishTail
? }
}
fragment animalName on Animal {
? name
}
fragment dogLegs on Dog{
? legs
}
fragment fishTail on Fish{
? tailColor
}
```
> 在聯(lián)合(union)查詢,因?yàn)闆]有公共字段棺耍,使用fragment示例如下 (:8004)
```typescript
{
? animals{
? ? ... on Dog{
? ? ? chinaName
? ? ? legs
? ? }
? ? ... on Fish{
? ? ? englishName
? ? ? tailColor
? ? }
? }
}
```
14贡未、別名(Alias)
> GraphQL支持別名命名,這對于查詢多個雷同的結(jié)果非常有用蒙袍,格式為 aliasName: orginName 如 (:8002)
```typescript
{
? james:user(id:1) {
? ? name
? ? sex
? ? intro
? ? skills
? ? MM:stature(unit:MM)
? ? mm:stature(unit:mm)
? }
? zhaiqianfeng:user(id:0) {
? ? name
? ? sex
? ? intro
? ? skills
? ? MM:stature(unit:MM)
? ? mm:stature(unit:mm)
? }
}
```
> 以上分別把id為1俊卤、2的分別給予別名為james、zhaiqianfeng害幅,以米(MM)消恍、毫米(mm)為單位的身高分別給予別名MM、mm以现,服務(wù)端返回的數(shù)據(jù)如
```typescript
{
? "data": {
? ? "james": {
? ? ? "name": "James",
? ? ? "sex": "男",
? ? ? "intro": "zhaiqianfeng的英文名",
? ? ? "skills": [
? ? ? ? "Linux",
? ? ? ? "Java",
? ? ? ? "nodeJs",
? ? ? ? "前端"
? ? ? ],
? ? ? "MM": 1.8,
? ? ? "mm": 180
? ? },
? ? "zhaiqianfeng": {
? ? ? "name": "zhaiqianfeng",
? ? ? "sex": "男",
? ? ? "intro": "博主狠怨,專注于Linux,Java,nodeJs,Web前端:Html5,JavaScript,CSS3",
? ? ? "skills": [
? ? ? ? "Linux",
? ? ? ? "Java",
? ? ? ? "nodeJs",
? ? ? ? "前端"
? ? ? ],
? ? ? "MM": 1.8,
? ? ? "mm": 180
? ? }
? }
}
```
15佩抹、 解析(resolve)
> GrapQL API有一個入口點(diǎn),通常稱為“Root”取董,而查詢都是由Root開始,如服務(wù)端定義一個root
```typescript
var root= {
? ? user: {
? ? ? ? name: 'zhaiqianfeng',
? ? ? ? sex: '男',
? ? ? ? intro: '博主无宿,專注于Linux,Java,nodeJs,Web前端:Html5,JavaScript,CSS3'
? ? }
};
```
> 在客戶端就可以使用如下查詢語句(:8001)
```typescript
{
? user(id:1) {
? ? name
? ? sex
? ? intro
? }
}
```
> 其實(shí)常用的是使用resolver茵汰,resolver是一個函數(shù),用來處理數(shù)據(jù)孽鸡,GraphQL規(guī)范中resolver的原型是 function(obj,args,context) :
> * obj 代表上一個對象蹂午,一般不常用;
> * args 傳入的參數(shù)彬碱;
> * context 上下文豆胸,比如session、數(shù)據(jù)庫鏈接等巷疼;
> 比如express-graphql的是 function(args,context) 晚胡,省略了第一個obj。因此上例中你可以使用resolver來處理嚼沿,比如:
```typescript
var root= {
? ? user: function(args,context){
? ? ? ? return {
? ? ? ? ? ? name: 'zhaiqianfeng',
? ? ? ? ? ? sex: '男',
? ? ? ? ? ? intro: '博主估盘,專注于Linux,Java,nodeJs,Web前端:Html5,JavaScript,CSS3'
? ? ? ? }
? ? }
};
```