理解 GraphQL 類型系統

GraphQL 最初于 2012 年在 Facebook 開發,作為針對動力不足的移動設備的更好的數據獲取解決方案,GraphQL 于 2015 年開源。作為一種為靈活性而設計的 API 技術,GraphQL 是 API 的開發人員和消費者以及他們背后的組織的強大推動者。GraphQL 實現的所有細節和功能都在 GraphQL Schema 中列出。為了編寫一個有效的 GraphQL schema,必須理解好 GraphQL 類型系統。
在本文中,將學習 GraphQL 類型:五種內置標量(scalar)類型、枚舉(enums)、列表(list)和非空包裝(non-null)類型、對象(object)類型以及與它們一起工作的抽象接口和聯合類型(union)。
標量類型
GraphQL 模式中的所有數據最終都解析為各種標量類型,代表原始值。GraphQL 響應可以看作一棵樹,標量類型是樹末端的葉子。嵌套響應中可以有多個級別,但最后一個級別將始終解析為標量(或枚舉)類型。 GraphQL 帶有五種內置標量類型:Int、Float、String、Boolean 和 ID。
Int
Int 是帶符號的 32 位非小數值,它是不包括小數的帶符號(正或負)整數。帶符號的 32 位整數的最大值為 2,147,483,647。這是用于數值數據的兩個內置標量之一。
Float
Float 是帶符號的雙精度小數值。它是一個帶小數點的有符號(正或負)數,例如 1.2,這是用于數值數據的另一個內置標量。
String
String 是 UTF-8 字符序列。 String 類型用于任何文本數據,這也可以包括非常大的數字等數據。大多數自定義標量都是字符串數據類型。
Boolean
Boolean 包含 true 和 false。
ID
ID 是唯一標識符,始終序列化為字符串,即使 ID 是數字也是如此。ID 類型通常可以用通用唯一標識符 (UUID) 表示。
自定義標量
除了上述這些內置標量之外,還可以使用 scalar 關鍵字來定義自定義標量。可以使用自定義標量來創建具有額外服務器級別驗證的類型,例如 Date、Time 或 Url。下面是一個定義新 Date 類型的示例:
scalar Date
服務器將知道如何使用 GraphQLScalarType 處理與這種新類型的交互。
枚舉(Enum)類型
Enum 類型,也稱為 Enumerator 類型,用于描述了一組可能的值。
例如可以為游戲角色的 Job 和 Species 創建一個枚舉,其中包含系統將接受的所有值。
"角色的工作等級"
enum Job {
FIGHTER
WIZARD
}
"性格的種類或血統"
enum Species {
HUMAN
ELF
DWARF
}
通過定義枚舉類型可以保證角色的 Job 只能是 FIGHTER 或 WIZARD,并且永遠不會意外地成為其他一些隨機字符串,如果使用 String 類型而不是 Enum,那么就有可能是別的隨機字符串。
枚舉也可以用作參數中的可接受值。例如,可以制作一個 Hand 枚舉來表示武器是單手(如短劍)還是雙手(如重斧),并使用它來確定是否可以裝備一個或兩個:
enum Hand {
SINGLE
DOUBLE
}
"戰士使用的一種武器"
type Weapon {
name: String!
attack: Int
range: Int
hand: Hand
}
type Query {
weapons(hand: Hand = SINGLE): [Weapon]
}
Hand 枚舉已聲明為 SINGLE 和 DOUBLE 作為值,weapons 字段上的參數具有默認值 SINGLE,這意味著如果未傳遞任何參數,它將回默認為 SINGLE。
非空類型
可能會注意到內置標量列表中缺少 null 或 undefined(一種被許多語言視為原始類型的常見類型)。 Null 在 GraphQL 中確實存在,表示缺少一個值。默認情況下,GraphQL 中的所有類型都可以為 null,因此 null 是對任何類型的有效響應。為了使值成為必需值,必須將其轉換為帶有尾隨感嘆號的 GraphQL 非空類型。 Non-Null 被定義為類型修飾符,這些類型用于修飾它所引用的類型。例如,String 是一個可選的(或可為空的)字符串,而 String! 是必需的(或非空的)字符串。
列表類型
GraphQL 中的 List 類型是另一種類型修飾符。任何用方括號 ([]) 括起來的類型都會成為 List 類型,這是一個定義列表中每個項目類型的集合,像 JavaScript 中的數組。
例如,定義為 [Int] 的類型意味著這個集合所有元素的類型為 Int 類型,[String] 將是 String 類型的集合。 Non-Null 和 List 可以一起使用,使一個類型既需要又定義為 List,例如 [String]!。
對象類型
如果 GraphQL 標量類型描述分層 GraphQL 響應末尾的“葉子”,那么對象類型描述中間的 分支,并且 GraphQL 模式中的幾乎所有內容都是一種對象類型。
對象由命名字段(鍵)列表和每個字段將解析為的值類型組成。對象是用 type 關鍵字定義的。至少要定義一個或多個字段,字段不能以兩個下劃線(__)開頭,以免與GraphQL自省系統沖突。
例如創建一個 Fighter 對象來表示游戲中的一種角色:
"具有直接戰斗能力和力量的英雄"
type Fighter {
id: ID!
name: String!
level: Int
active: Boolean!
}
在此示例中,聲明了 Fighter 對象類型,定義了 4 個字段:
id:非空ID類型。name:非空字符串類型。level:Int類型。active:非空布爾類型。
在聲明上方,可以使用雙引號添加注釋,如本例:具有直接戰斗能力和力量的英雄,這將顯示為類型的描述。
在此示例中,每個字段都解析為標量類型,但對象字段也可以解析為其他對象類型。例如,可以創建一個 Weapon 類型,并且可以設置 GraphQL 模式,其中 Fighter 上的 weapon 字段將解析為一個 Weapon 對象:
"戰士使用的一種武器"
type Weapon {
name: String!
attack: Int
range: Int
}
"具有直接戰斗能力和力量的英雄"
type Fighter {
id: ID!
name: String!
level: Int
active: Boolean!
weapon: Weapon
}
對象也可以嵌套到其他對象的字段中。
根操作類型
有三種特殊對象作為 GraphQL schema 的入口點:Query、Mutation 和 Subcription。這些被稱為根操作類型,并遵循與任何其他對象類型相同的規則。
schema 關鍵字表示 GraphQL 模式的入口點。根 Query、Mutation 和 Subcription 類型將位于根模式對象上:
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
Query 類型在任何 GraphQL 模式上都是必需的,代表一個讀取請求,類似于 REST API GET。以下是返回 Fighter 類型列表的根查詢對象的示例:
type Query {
fighters: [Fighter]
}
Mutations 代表寫入請求,類似于 REST API 中的 POST、PUT 或 DELETE。在以下示例中,Mutation 有一個帶有命名參數(輸入)的 addFighter 字段:
type Mutation {
addFighter(input: FighterInput): Fighter
}
最后,一個 Subscription 對應于一個事件流,它將與 Web 應用程序中的 Websocket 結合使用。如下所示:
type Subscription {
randomBattle(enemy: Enemy): BattleResult
}
請注意,schema 入口點通常在某些 GraphQL 實現中被抽象掉。
字段參數
GraphQL 對象的字段本質上是返回值的函數,并且它們可以像任何函數一樣接受參數。字段參數由參數名稱后跟類型定義,參數可以是任何非對象類型。在此示例中,可以通過 id 字段(解析為非空 ID 類型)過濾 Fighter 對象:
type Query {
fighter(id: ID!): Fighter
}
這個特定示例對于從數據存儲中獲取單個項目很有用,但參數也可用于過濾、分頁和其他更具體的查詢。
接口類型
與 Object 類型一樣,抽象接口類型由一系列命名字段及其關聯的值類型組成。接口看起來像并遵循與對象相同的所有規則,但用于定義對象實現的子集。
到目前為止,在 schema 中有一個 Fighter 對象,但可能還想創建一個Wizard、一個 Healer 和其他對象,它們將共享大部分相同的字段但還是存在一些差異。在這種情況下,可以使用接口來定義它們共有的字段,并創建作為接口實現的對象。
在下面的示例中,使用 interface 關鍵字創建 BaseCharacter 接口,其中包含每種類型的字符將擁有的所有字段:
"A hero on a quest."
interface BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job
}
每個角色類型都有字段 id、name、level、species 和 job。
現在,假設有一個具有這些共享字段的 Fighter 類型和一個 Wizard 類型,但是 Fighters 使用 Weapon 而 Wizards 使用 Spells。可以使用 implements 關鍵字將每個描述為 BaseCharacter 實現,這意味著它們必須具有創建的接口中的所有字段:
type Fighter implements BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job!
weapon: Weapon
}
type Wizard implements BaseCharacter {
id: ID!
name: String!
level: Int!
species: Species
job: Job!
spells: [Spell]
}
Fighter 和 Wizard 都是 BaseCharacter 接口的有效實現,因為它們具有所需的字段子集。
Union 類型
可以與對象一起使用的另一種抽象類型是 union 類型。使用 union 關鍵字,可以定義一個類型,其中包含所有有效響應的對象列表。
使用上面創建的接口,可以創建一個 Character union,將 character 定義為 Wizard 或 Fighter :
union Character = Wizard | Fighter
等號 = 設置定義,管道符 | 用作 OR 語句。請注意,union 必須由對象或接口組成,標量類型在 union 上無效。
現在,如果查詢 characters 列表,它可以使用 Character union 并返回所有 Wizard 和 Fighter 類型。
總結
上面學習了定義 GraphQL 類型系統的類型,包括最基本的類型是標量類型由 Int、Float、String、Boolean、ID和 GraphQL 實現創建的任何自定義標量類型組成。枚舉是有效常量值的列表,當需要對查詢響應進行更多控制時,可以使用枚舉,而不是簡單地將其聲明為字符串。列表類型和非空類型被稱為類型修飾符 type modifier 或包裝類型 wrapping type,它們分別可以將其他類型定義為集合類型或必需類型。GraphQL schema 中的幾乎所有內容都是對象類型,包括 query、mutation 和 subscription 入口點。接口和聯合類型是抽象類型,在定義對象時很有用。