對於GraphQL 的使用語法在上一節中已經大概介紹了基本的使用方式了,這一篇將會對上一篇入門作拓展,努力將全部的使用語法都覆蓋到。前端
首先是介紹在前端查詢時用的語法,分紅Query 和Mutation 兩部分,Subscription 的和Query 是相似的就不特別說明了。數據庫
假設咱們如今有一個數據集,結構是這樣的:segmentfault
classDiagram Student <|-- Teacher2Student Teacher <|-- Teacher2Student class Student { +Int id +String name +Int age +teachers(id_eq: number) get_all_teachers } class Teacher { +Int id +String name +Boolean gender +students(name_contains: string) get_all_students } class Teacher2Student { +Int id +Int student_id +Int teacher_id +student() get_student +teacher() get_teacher }
首先最簡單的使用方式咱們可查:數組
query { student { id name teacher { id name } } } # 結果: { 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: "吳老師" } ] } ] } }
咱們經過上面的查詢能夠獲得總共有兩個學生,其中李老師同時教了他們兩人。這是最最基本的用法。下面咱們慢慢加查詢需求去改變結果。函數
咱們能夠經過添加參數的方式限制返回集的內容post
query { student(name_contains: "三") { # <-- 這裏限制了只要名字中包含了「三」的學生 id name teacher(id_eq: 2) { # <-- 這裏限制了只要id=2 的老師 id name } } } # 結果: { data: { student: [ { id: 1, name: "張三", teacher: [ { id: 2, name: "吳老師" } ] }, { id: 3, name: "王三", teacher: [ { id: 2, name: "吳老師" } ] } ] } }
這時,由於咱們過濾了只要id 爲2 的老師,因此李老師就給過濾掉了;由於設置了過濾只要名字中帶「三」字的學生,因此李四也給過濾掉了。編碼
同理,也能夠用參數去對內容進行分頁、跳過等操做,操做相同就不寫例子了。url
由於在查詢中,不一樣的數據實體在Graphql 語句中自己是相似於直接請求這個單元的存在,因此若是你同時請求兩個相同的集時它就會報錯;由於它們都應該是一一對應的。這時你就能夠用別名來解決這個問題:scala
query { san: student(name_contains: "三") { id name } wang: student(name_contains: "王") { id name } } # 結果: { data: { san: [ { id: 1, name: "張三" } ] wang: [ { id: 3, name: "王三" } ] } }
處理請求結果的時候要注意用了別名的內容是以別名爲key 返回的,不是原來的名字了。翻譯
看上面的查詢語句,咱們能夠看到當用了不一樣的別名時咱們不免會產生這種寫了一堆重複的字段名的狀況。咱們這個例子字段少還好,但正常的業務要有個幾十個字段都是挺常見的,這樣寫可就太費勁了,這時就能夠祭出Fragments 來處理了:
fragment studentFields on Student { id name } query { san: student(name_contains: "三") { ...studentFields } wang: student(name_contains: "王") { ...studentFields } } # 結果: { data: { san: [ { id: 1, name: "張三" } ] wang: [ { id: 3, name: "王三" } ] } }
這個相對來講是比較少用的,起碼我我的的使用狀況來看,我基本更傾向於「能省就省」的原則;但寫教程的話就仍是介紹下吧。主要出如今同時有多個操做的狀況下,用於區分操做數據。
query op1 { student(name_contains: "三") { id } } query op2 { student(name_contains: "王") { id } } # op1, op2 就是操做名。 # 但平常寫query 你甚至能夠將操做符("query")也省了像下面這樣寫就行 { student { id } }
這個參數有別於上面提到的Arguments, Arguments 是用於具體數據結點操做用的。Variables 指的是面向操做符時,可讓Query 變得可複用,也方便在不一樣地方使用。
假設咱們有兩個不一樣的頁面,都要查詢學生表但過濾不一樣,這時若是咱們寫兩個查詢像下面這樣就很浪費,代碼也很醜,也不能複用。
# 頁面一用的 query { student(name_contains: "三") { id } } # 頁面二用的 query { student(name_contains: "王") { id } }
由於本質上說,它們查詢的內容是相同的,只是參數有點不同,這裏咱們能夠把參數給提取出來,經過在實際使用時再由不一樣狀況傳參就好:
# 頁面1、二都用同一個Query query($name: String) { student(name_contains: $name) { id } }
使用時改變傳進去的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: "王"})
這樣出來的結果就和上面寫兩個不一樣的Query 是同樣的,但代碼會優雅不少,Query 也獲得了合理複用。若是有一天須要修改請求的返回結果,也不用跑到各個地方一個一個地修改請求的Query.
注意定義參數有幾個硬性規定:
# 這樣若是沒有給任何參數則$name 會默認等於「三」 query($name: String = "三") { student(name_contains: $name) { id } }
Directives 能夠翻譯成指示符,但我以爲不太直觀,它的功能主要是相似一個條件修飾,相似代碼中的if-else 塊差很少的功能。讓你能夠在外面指定要怎麼請求的細節。
query($name: String, $withTeacher: Boolean!) { student(name_contains: $name) { id teacher @include(if: $withTeacher) { id } } }
它的主要做用就是說,若是你在外面的variables 中給定withTeacher=true 那它就會請求teacher 節點,等同於:
query($name: String) { student(name_contains: $name) { id teacher { id } } }
反之,若是指定withTeacher=false 那它就會省略teacher 節點,等同於:
query($name: String) { student(name_contains: $name) { id } }
Directives 主要有兩個操做符:@include(if: Boolean)
和 @skip(if: Boolean)
這兩個的做用相反。另外Directives 這個功能須要服務端有相關支持才能用。但同時,若是須要服務端也能夠自已實現徹底自定義的Directives.
這個和Query 那邊的規則徹底同樣,參見上面的內容便可,給個小例子:
# 無參寫法 mutation create { createStudent(name: "王五", age: 18) { id } } # 有參寫法 mutation create($name: String, $age: Int) { createStudent(name: $name, age: $age) { id } } # 另外一種有參寫法 # 假設createStudent 函數的參數的類型叫createStudentInput mutation create($input: createStudentInput!) { createStudent($input) { id } }
這裏的使用情景主要是針對聯合(Union) 類型的,相似於接口(interface) 與類(class)的關係。
假設咱們有個接口叫動物(Animal), 有兩個類分別是狗(Dog) 和鳥(Bird). 而且咱們將這兩個類由一個GraphQL 節點給出去:
{ animal { name kind ... on Dog { breed } ... on Bird { wings } } } # 結果 { data: { animal: [ { name: "Pepe", kind: "Dog", breed: "Husky" }, { name: "Pipi", kind: "Bird", wings: 2 } ] } }
從上面的結果能夠看出,它能夠由不一樣的類型去查不一樣的「類」,但返回時能夠合併返回。就相似因而從一個「接口」 上直接獲取到實現類的數據了,很是具體。但大部分狀況下咱們可能不會合並着查兩個不一樣結構的數據以一個數組返回,咱們更多多是用在於用同一個節點名(animal)就能夠查不一樣的東西但先以他們的類型做了過濾。
配合上面的例子食用,若是咱們沒有kind 那個字段時,咱們要怎麼知道哪一個元素是哪一個類型呢?咱們能夠用元字段去知道咱們當前操做的是哪一個數據實體,主要的元字段有 __typename
.
咱們能夠這樣查:
{ animal { name __typename ... on Dog { breed } ... on Bird { wings } } } # 結果 { data: { animal: [ { name: "Pepe", __typename: "Animal__Dog", breed: "Husky" }, { name: "Pipi", __typename: "Animal__Bird", wings: 2 } ] } }
__typename
是內置的,你能夠在任何節點上查,它都會給你一個類型。
咱們知道GraphQL 是一個靜態類型的語法系統,那麼咱們在真正使用前就必須先定義好它的類型。
GraphQL 的類型定義叫作Schemas. 有它自已獨立的語法。裏面有各個基本類型Scalar,能夠定義成不一樣對象的Type. 也能夠自已用基本類型定義成新的類型。
全部的不一樣的對象最終會組成一個樹狀的結構,根由schema 組成:
schema { query: Query mutation: Mutation }
而後再定義裏面一層一層的子對象,好比咱們上面那個模型大概能夠寫成:
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 }
像上面這樣咱們就定義了兩個不一樣的對象及他們的屬性。其中,若是是必填或者說非空的字段則帶有"!" 在它的類型後面,好比id: ID!
就代表id 是個非空的字段。非空的字段若是在操做中給它傳null
會報錯。另外某種類型組成的數組能夠用類型加中括號組成,好比上面的Student 裏面的Teacher.
定義一個字段爲數組:
myField: [String!]
這樣定義呢,代表了這個字段自己是能夠爲 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
GraphQL 默認的自帶類型只有5 種。分別是:
ID: 就相似傳統數據庫中的ID 字段,主要用於區別不一樣的對象。能夠直接是一個Int, 也多是一個編碼過的惟一值,好比常見的relay 中使用的是「類名:ID」 的字符串再經base64 轉碼後的結果做爲ID. 要注意的是這個ID 只是存在於Graphql 中的。它不必定和數據庫中的是對應的。好比relay 這個狀況,數據庫中存的可能仍是一個整數並非那個字符串。
Int: 整數,可正負。
Float: 雙精度浮點數,可正負。
String: UTF-8 字符的字符串。
Boolean: true / false.
若是不能知足你的業務場景你就能夠自定義新的類型,或者是找第三方作好的拓展類型。
定義一個類型的Graphql 寫法很簡單,好比咱們新增一個Date 類型。
scalar Date
就這樣就能夠了,可是你還須要在你的代碼中實現它的具體功能,怎麼轉換出入運行時等等。
另外,Graphql 中支持枚舉類型,能夠這樣定義:
enum GenderTypes { MALE FEMALE OTHERS }
Interface 和 Union 很像,因此我就合在一塊兒講了。
Interface 和其餘語言的相似,都是爲了給一個通用的父類型定義用的。能夠像這樣定義及使用:
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 }
能夠看到,接口定義的每一個字段在實現時都會帶上,但它也能夠有自已的字段。查詢時,須要注意的是:你不能夠直接在Animal 上查到各個獨有的字段,由於當你在Animal 上作查詢時系統並不知道你當前查詢的對象是Dog 仍是Cat. 你須要用inline fragment 去指定。
# 這樣查直接報錯: # "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, 咱們再看看Union.
Union 你能夠直接理解成是沒有共同字段的Interface.
union Plant = Lily | Rose | Daisy
查詢時和接口同樣得用inline fragments 去指定類型。
上面在那個Mutation 的Variables 舉例子時稍微提到過,就是給某個操做的輸入的全部參數指定成一個類型,這樣能夠更方便地添加內容也增長了代碼可複用的程度。
假設咱們有一個Mutation 的定義是這樣的:
type Mutaion { createSomething(foo: Int, bar: Float): Something }
使用Input types:
input CreateSomethingInput { foo: Int bar: Float } type Mutaion { createSomething(input: CreateSomethingInput): Something }