深入GraphQL 的使用語法

深入GraphQL 的使用語法

對(duì)于GraphQL 的使用語法在上一節(jié)中已經(jīng)大概介紹了基本的使用方式了账胧,這一篇將會(huì)對(duì)上一篇入門做拓展吹截,努力將所有的使用語法都覆蓋到。

1. 終端語法

首先是介紹在前端查詢時(shí)用的語法甫窟,分成Query 和Mutation 兩部分,Subscription 的和Query 是類似的就不特別說明了捌木。

1.1 Query

假設(shè)我們現(xiàn)在有一個(gè)數(shù)據(jù)集,結(jié)構(gòu)是這樣的:

  1. 學(xué)生和老師各有各自的特有字段职辅;
  2. 學(xué)生中有個(gè)獲取所有相關(guān)老師的方法,老師中也有一個(gè)獲取所有學(xué)生的方法聂示;
  3. 學(xué)生和老師互為多對(duì)多關(guān)系域携;
Snipaste_2021-07-07_15-38-35.png

首先最簡單的使用方式我們可查:

query {
  student {
    id
    name
    teacher {
      id
      name
    }
  }
}

# 結(jié)果:

{
  data: {
    student: [
      {
        id: 1,
        name: "張三",
        teacher: [
          {
            id: 1,
            name: "李老師"
          },
          {
            id: 2,
            name: "吳老師"
          }
        ]
      },
      {
        id: 2,
        name: "李四",
        teacher: [
          {
            id: 1,
            name: "李老師"
          }
        ]
      },
      {
        id: 3,
        name: "王三",
        teacher: [
          {
            id: 2,
            name: "吳老師"
          }
        ]
      }
    ]
  }
}

我們通過上面的查詢可以得到總共有兩個(gè)學(xué)生,其中李老師同時(shí)教了他們兩人鱼喉。這是最最基本的用法秀鞭。下面我們慢慢加查詢需求去改變結(jié)果。

1.1.1 參數(shù) Arguments

我們可以通過添加參數(shù)的方式限制返回集的內(nèi)容

query {
  student(name_contains: "三") {  # <-- 這里限制了只要名字中包含了“三”的學(xué)生
    id
    name
    teacher(id_eq: 2) {  # <-- 這里限制了只要id=2 的老師
      id
      name
    }
  }
}

# 結(jié)果:

{
  data: {
    student: [
      {
        id: 1,
        name: "張三",
        teacher: [
          {
            id: 2,
            name: "吳老師"
          }
        ]
      },
      {
        id: 3,
        name: "王三",
        teacher: [
          {
            id: 2,
            name: "吳老師"
          }
        ]
      }
    ]
  }
}

這時(shí)扛禽,因?yàn)槲覀冞^濾了只要id 為2 的老師锋边,所以李老師就給過濾掉了;因?yàn)樵O(shè)置了過濾只要名字中帶“三”字的學(xué)生编曼,所以李四也給過濾掉了豆巨。

同理,也可以用參數(shù)去對(duì)內(nèi)容進(jìn)行分頁灵巧、跳過等操作抹沪,操作相同就不寫例子了刻肄。

1.1.2 別名 Aliases

因?yàn)樵诓樵冎校煌臄?shù)據(jù)實(shí)體在Graphql 語句中本身是類似于直接請(qǐng)求這個(gè)單元的存在融欧,所以如果你同時(shí)請(qǐng)求兩個(gè)相同的集時(shí)它就會(huì)報(bào)錯(cuò)敏弃;因?yàn)樗鼈兌紤?yīng)該是一一對(duì)應(yīng)的。這時(shí)你就可以用別名來解決這個(gè)問題:

query {
  san: student(name_contains: "三") {
    id
    name
  }
  wang: student(name_contains: "王") {
    id
    name
  }
}

# 結(jié)果:

{
  data: {
    san: [
      {
        id: 1,
        name: "張三"
      }
    ]
    wang: [
      {
        id: 3,
        name: "王三"
      }
    ]
  }
}

處理請(qǐng)求結(jié)果的時(shí)候要注意用了別名的內(nèi)容是以別名為key 返回的噪馏,不是原來的名字了麦到。

1.1.3 片段 Fragments

看上面的查詢語句,我們可以看到當(dāng)用了不同的別名時(shí)我們難免會(huì)產(chǎn)生這種寫了一堆重復(fù)的字段名的情況欠肾。我們這個(gè)例子字段少還好瓶颠,但正常的業(yè)務(wù)要有個(gè)幾十個(gè)字段都是挺常見的,這樣寫可就太費(fèi)勁了刺桃,這時(shí)就可以祭出Fragments 來處理了:

fragment studentFields on Student {
    id
    name
}

query {
  san: student(name_contains: "三") {
    ...studentFields
  }
  wang: student(name_contains: "王") {
    ...studentFields
  }
}

# 結(jié)果:

{
  data: {
    san: [
      {
        id: 1,
        name: "張三"
      }
    ]
    wang: [
      {
        id: 3,
        name: "王三"
      }
    ]
  }
}

1.1.4 操作名 Operation name

這個(gè)相對(duì)來說是比較少用的粹淋,起碼我個(gè)人的使用情況來看,我基本更傾向于“能省就省”的原則瑟慈;但寫教程的話就還是介紹下吧桃移。主要出現(xiàn)在同時(shí)有多個(gè)操作的情況下,用于區(qū)分操作數(shù)據(jù)葛碧。

query op1 {
  student(name_contains: "三") {
    id
  }
}

query op2 {
  student(name_contains: "王") {
    id
  }
}

# op1, op2 就是操作名借杰。
# 但日常寫query 你甚至可以將操作符("query")也省了像下面這樣寫就行

{
  student {
    id
  }
}

1.1.5 操作參數(shù) Variables

這個(gè)參數(shù)有別于上面提到的Arguments, Arguments 是用于具體數(shù)據(jù)結(jié)點(diǎn)操作用的。Variables 指的是面向操作符時(shí)进泼,可以讓Query 變得可復(fù)用蔗衡,也方便在不同地方使用纤虽。

假設(shè)我們有兩個(gè)不同的頁面,都要查詢學(xué)生表但過濾不同绞惦,這時(shí)如果我們寫兩個(gè)查詢像下面這樣就很浪費(fèi)廓推,代碼也很丑,也不能復(fù)用翩隧。

# 頁面一用的
query {
  student(name_contains: "三") {
    id
  }
}

# 頁面二用的
query {
  student(name_contains: "王") {
    id
  }
}

因?yàn)楸举|(zhì)上說樊展,它們查詢的內(nèi)容是相同的,只是參數(shù)有點(diǎn)不一樣堆生,這里我們可以把參數(shù)給提取出來妇穴,通過在實(shí)際使用時(shí)再由不同情況傳參就好:

# 頁面一阎曹、二都用同一個(gè)Query
query($name: String) {
  student(name_contains: $name) {
    id
  }
}

使用時(shí)改變傳進(jìn)去的Variables, 例:

const query = gql`
  query($name: String) {
    student(name_contains: $name) {
      id
    }
  }
`

const page1 = post(URL, query=query, variables={name: "三"})
const page2 = post(URL, query=query, variables={name: "王"})

這樣出來的結(jié)果就和上面寫兩個(gè)不同的Query 是一樣的,但代碼會(huì)優(yōu)雅很多,Query 也得到了合理復(fù)用财忽。如果有一天需要修改請(qǐng)求的返回結(jié)果,也不用跑到各個(gè)地方一個(gè)一個(gè)地修改請(qǐng)求的Query.

注意定義參數(shù)有幾個(gè)硬性規(guī)定:

  1. 參數(shù)名要以 $ 開頭厢拭。沒得商量不以美元符開頭它不認(rèn)的它匕。
  2. 參數(shù)的類型必須和它將會(huì)用到的地方的類型一樣,否則會(huì)出錯(cuò)寞射。因?yàn)镚raphql 是靜態(tài)類型的語言渔工。
  3. 可以以類似TS 的方式給參數(shù)默認(rèn)值,如下桥温。
# 這樣如果沒有給任何參數(shù)則$name 會(huì)默認(rèn)等于“三”
query($name: String = "三") {
  student(name_contains: $name) {
    id
  }
}

1.1.6 指示符 Directives

Directives 可以翻譯成指示符引矩,但我覺得不太直觀,它的功能主要是類似一個(gè)條件修飾侵浸,類似代碼中的if-else 塊差不多的功能旺韭。讓你可以在外面指定要怎么請(qǐng)求的細(xì)節(jié)。

query($name: String, $withTeacher: Boolean!) {
  student(name_contains: $name) {
    id
    teacher @include(if: $withTeacher) {
      id
    }
  }
}

它的主要作用就是說掏觉,如果你在外面的variables 中給定withTeacher=true 那它就會(huì)請(qǐng)求teacher 節(jié)點(diǎn)区端,等同于:

query($name: String) {
  student(name_contains: $name) {
    id
    teacher {
      id
    }
  }
}

反之,如果指定withTeacher=false 那它就會(huì)省略teacher 節(jié)點(diǎn)澳腹,等同于:

query($name: String) {
  student(name_contains: $name) {
    id
  }
}

Directives 主要有兩個(gè)操作符:@include(if: Boolean)@skip(if: Boolean)

這兩個(gè)的作用相反织盼。另外Directives 這個(gè)功能需要服務(wù)端有相關(guān)支持才能用。但同時(shí)遵湖,如果需要服務(wù)端也可以自已實(shí)現(xiàn)完全自定義的Directives.

1.2 Mutation

1.2.1 操作參數(shù) Variables

這個(gè)和Query 那邊的規(guī)則完全一樣悔政,參見上面的內(nèi)容即可,給個(gè)小例子:

# 無參寫法
mutation create {
  createStudent(name: "王五", age: 18) {
    id
  }
}

# 有參寫法
mutation create($name: String, $age: Int) {
  createStudent(name: $name, age: $age) {
    id
  }
}

# 另一種有參寫法
# 假設(shè)createStudent 函數(shù)的參數(shù)的類型叫createStudentInput
mutation create($input: createStudentInput!) {
  createStudent($input) {
    id
  }
}

1.2.2 行內(nèi)片段 Inline Fragments

這里的使用情景主要是針對(duì)聯(lián)合(Union) 類型的延旧,類似于接口(interface) 與類(class)的關(guān)系谋国。

假設(shè)我們有個(gè)接口叫動(dòng)物(Animal), 有兩個(gè)類分別是狗(Dog) 和鳥(Bird). 并且我們將這兩個(gè)類由一個(gè)GraphQL 節(jié)點(diǎn)給出去:

{
  animal {
    name
    kind
    ... on Dog {
      breed
    }
    ... on Bird {
      wings
    }
  }
}

# 結(jié)果

{
  data: {
    animal: [
      {
        name: "Pepe",
        kind: "Dog",
        breed: "Husky"
      },
      {
        name: "Pipi",
        kind: "Bird",
        wings: 2
      }
    ]
  }
}

從上面的結(jié)果可以看出,它可以由不同的類型去查不同的“類”迁沫,但返回時(shí)可以合并返回芦瘾。就類似于是從一個(gè)“接口” 上直接獲取到實(shí)現(xiàn)類的數(shù)據(jù)了捌蚊,非常具體。但大部分情況下我們可能不會(huì)合并著查兩個(gè)不同結(jié)構(gòu)的數(shù)據(jù)以一個(gè)數(shù)組返回近弟,我們更多可能是用在于用同一個(gè)節(jié)點(diǎn)名(animal)就可以查不同的東西但先以他們的類型作了過濾缅糟。

1.2.3 元字段 Meta fields

配合上面的例子食用,如果我們沒有kind 那個(gè)字段時(shí)祷愉,我們要怎么知道哪個(gè)元素是哪個(gè)類型呢窗宦?我們可以用元字段去知道我們當(dāng)前操作的是哪個(gè)數(shù)據(jù)實(shí)體,主要的元字段有 __typename.

我們可以這樣查:

{
  animal {
    name
    __typename
    ... on Dog {
      breed
    }
    ... on Bird {
      wings
    }
  }
}

# 結(jié)果

{
  data: {
    animal: [
      {
        name: "Pepe",
        __typename: "Animal__Dog",
        breed: "Husky"
      },
      {
        name: "Pipi",
        __typename: "Animal__Bird",
        wings: 2
      }
    ]
  }
}

__typename 是內(nèi)置的二鳄,你可以在任何節(jié)點(diǎn)上查赴涵,它都會(huì)給你一個(gè)類型。

2. 類型定義

2.1 基礎(chǔ)

我們知道GraphQL 是一個(gè)靜態(tài)類型的語法系統(tǒng)订讼,那么我們?cè)谡嬲褂们熬捅仨毾榷x好它的類型髓窜。
GraphQL 的類型定義叫做Schemas. 有它自已獨(dú)立的語法。里面有各個(gè)基本類型Scalar欺殿,可以定義成不同對(duì)象的Type. 也可以自已用基本類型定義成新的類型寄纵。

所有的不同的對(duì)象最終會(huì)組成一個(gè)樹狀的結(jié)構(gòu),根由schema 組成:

schema {
  query: Query
  mutation: Mutation
}

然后再定義里面一層一層的子對(duì)象脖苏,比如我們上面那個(gè)模型大概可以寫成:

type Query {
  student(name_contains: String): Student
  teacher(id_eq: ID): Teacher
}

type Student {
  id: ID!
  name: String!
  age: Int
  teachers: [Teacher!]!
}

type Teacher {
  id: ID!
  name: String!
  gender: Boolean
}

像上面這樣我們就定義了兩個(gè)不同的對(duì)象及他們的屬性程拭。其中,如果是必填或者說非空的字段則帶有"!" 在它的類型后面帆阳,比如id: ID! 就表明id 是個(gè)非空的字段哺壶。非空的字段如果在操作中給它傳null 會(huì)報(bào)錯(cuò)屋吨。另外某種類型組成的數(shù)組可以用類型加中括號(hào)組成蜒谤,比如上面的Student 里面的Teacher.

定義一個(gè)字段為數(shù)組:

myField: [String!]

這樣定義呢,表明了這個(gè)字段本身是可以為 null 的至扰,但它不能有 null 的成員鳍徽。比如說:

const myField: null // valid
const myField: [] // valid
const myField: ['a', 'b'] // valid
const myField: ['a', null, 'b'] // error

但如果,是這樣定義的:

myField: [String]!

則代表它本身不能為 null 但它的組成成員中可以包含 null .

const myField: null // error
const myField: [] // valid
const myField: ['a', 'b'] // valid
const myField: ['a', null, 'b'] // valid

2.2 自帶類型

GraphQL 默認(rèn)的自帶類型只有5 種敢课。分別是:

ID: 就類似傳統(tǒng)數(shù)據(jù)庫中的ID 字段阶祭,主要用于區(qū)別不同的對(duì)象≈备眩可以直接是一個(gè)Int, 也可能是一個(gè)編碼過的唯一值濒募,比如常見的relay 中使用的是“類名:ID” 的字符串再經(jīng)base64 轉(zhuǎn)碼后的結(jié)果作為ID. 要注意的是這個(gè)ID 只是存在于Graphql 中的。它不一定和數(shù)據(jù)庫中的是對(duì)應(yīng)的圾结。比如relay 這個(gè)情況瑰剃,數(shù)據(jù)庫中存的可能還是一個(gè)整數(shù)并不是那個(gè)字符串。

Int: 整數(shù)筝野,可正負(fù)晌姚。

Float: 雙精度浮點(diǎn)數(shù)粤剧,可正負(fù)。

String: UTF-8 字符的字符串挥唠。

Boolean: true / false.

如果不能滿足你的業(yè)務(wù)場(chǎng)景你就可以自定義新的類型抵恋,或者是找第三方做好的拓展類型。

定義一個(gè)類型的Graphql 寫法很簡單宝磨,比如我們新增一個(gè)Date 類型弧关。

scalar Date

就這樣就可以了,但是你還需要在你的代碼中實(shí)現(xiàn)它的具體功能唤锉,怎么轉(zhuǎn)換出入運(yùn)行時(shí)等等梯醒。

另外,Graphql 中支持枚舉類型腌紧,可以這樣定義:

enum GenderTypes {
  MALE
  FEMALE
  OTHERS
}

2.3 接口(Interface) 和聯(lián)合類型(Union)

Interface 和 Union 很像茸习,所以我就合在一起講了。

Interface 和其他語言的類似壁肋,都是為了給一個(gè)通用的父類型定義用的号胚。可以像這樣定義及使用:

interface Animal {
  id: ID!
  name: String
}

type Dog implements Animal {
  id: ID!
  name: String
  breed: String
}

type Cat implements Animal {
  id: ID!
  name: String
  color: String
}

可以看到浸遗,接口定義的每個(gè)字段在實(shí)現(xiàn)時(shí)都會(huì)帶上猫胁,但它也可以有自已的字段。查詢時(shí)跛锌,需要注意的是:你不可以直接在Animal 上查到各個(gè)獨(dú)有的字段弃秆,因?yàn)楫?dāng)你在Animal 上做查詢時(shí)系統(tǒng)并不知道你當(dāng)前查詢的對(duì)象是Dog 還是Cat. 你需要用inline fragment 去指定。

# 這樣查直接報(bào)錯(cuò):
# "Cannot query field \"color\" on type \"Animal\". Did you mean to use an inline fragment on \"Cat\"?"
query {
  animal {
    id
    name
    color
  }
}

# 正確的打開方式:
query {
  animal {
    id
    name
    ... on Cat {
      color
    }
  }
}

講完Interface, 我們?cè)倏纯碪nion.
Union 你可以直接理解成是沒有共同字段的Interface.

union Plant = Lily | Rose | Daisy

查詢時(shí)和接口一樣得用inline fragments 去指定類型髓帽。

2.4 輸入類型 Input types

上面在那個(gè)Mutation 的Variables 舉例子時(shí)稍微提到過菠赚,就是給某個(gè)操作的輸入的所有參數(shù)指定成一個(gè)類型,這樣可以更方便地添加內(nèi)容也增加了代碼可復(fù)用的程度郑藏。

假設(shè)我們有一個(gè)Mutation 的定義是這樣的:

type Mutaion {
  createSomething(foo: Int, bar: Float): Something
}

使用Input types:

input CreateSomethingInput {
  foo: Int
  bar: Float
}

type Mutaion {
  createSomething(input: CreateSomethingInput): Something
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末衡查,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子必盖,更是在濱河造成了極大的恐慌拌牲,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歌粥,死亡現(xiàn)場(chǎng)離奇詭異塌忽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)失驶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門土居,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事装盯】缆牵” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵埂奈,是天一觀的道長迄损。 經(jīng)常有香客問我,道長账磺,這世上最難降的妖魔是什么芹敌? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮垮抗,結(jié)果婚禮上氏捞,老公的妹妹穿的比我還像新娘。我一直安慰自己冒版,他們只是感情好液茎,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辞嗡,像睡著了一般捆等。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上续室,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天栋烤,我揣著相機(jī)與錄音,去河邊找鬼挺狰。 笑死明郭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的丰泊。 我是一名探鬼主播薯定,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼趁耗!你這毒婦竟也來了沉唠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤苛败,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后径簿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罢屈,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年篇亭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缠捌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖曼月,靈堂內(nèi)的尸體忽然破棺而出谊却,到底是詐尸還是另有隱情,我是刑警寧澤哑芹,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布炎辨,位于F島的核電站,受9級(jí)特大地震影響聪姿,放射性物質(zhì)發(fā)生泄漏碴萧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一末购、第九天 我趴在偏房一處隱蔽的房頂上張望破喻。 院中可真熱鬧,春花似錦盟榴、人聲如沸曹质。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咆繁。三九已至,卻和暖如春顶籽,著一層夾襖步出監(jiān)牢的瞬間玩般,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國打工礼饱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坏为,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓镊绪,卻偏偏與公主長得像匀伏,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝴韭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容