在這個頁面,您將瞭解所有需要知道的 GraphQL 類型系統,以及它如何描述可以查詢的資料。由於 GraphQL 可以與任何後端架構或程式語言一起使用,因此我們將避開特定於實作的細節,只討論概念。
如果您之前看過 GraphQL 查詢,您會知道 GraphQL 查詢語言基本上是關於選擇物件上的欄位。因此,例如,在以下查詢中
hero
欄位hero
傳回的物件,我們選擇 name
和 appearsIn
欄位由於 GraphQL 查詢的形狀與結果非常匹配,因此您可以在不知道太多伺服器的情況下預測查詢將傳回什麼。但是,對於我們可以要求的資料有一個確切的描述很有用 - 我們可以選擇哪些欄位?它們可能會傳回什麼類型的物件?這些子物件上有什麼欄位可用?這就是架構發揮作用的地方。
每個 GraphQL 服務定義一組類型,完整描述您可以在該服務上查詢的可能資料集。然後,當查詢進入時,會根據該架構驗證並執行查詢。
GraphQL 服務可以用任何語言撰寫。由於我們無法依賴特定程式語言語法(例如 JavaScript)來討論 GraphQL 架構,因此我們將定義自己的簡單語言。我們將使用「GraphQL 架構語言」- 它類似於查詢語言,並允許我們以與語言無關的方式討論 GraphQL 架構。
GraphQL 架構最基本的組成部分是物件類型,它僅表示您可以從服務中擷取的一種類型物件,以及它有哪些欄位。在 GraphQL 架構語言中,我們可能會這樣表示
type Character { name: String! appearsIn: [Episode!]!}
該語言相當易讀,但讓我們來探討一下,以便我們能有共同的詞彙
Character
是GraphQL 物件類型,表示它是一種具有某些欄位的類型。架構中的大多數類型將是物件類型。name
和 appearsIn
是 Character
類型上的欄位。這表示 name
和 appearsIn
是在操作 Character
類型的 GraphQL 查詢的任何部分中唯一可能出現的欄位。String
是內建純量類型之一 - 這些類型會解析為單一純量物件,且在查詢中無法有子選取。我們稍後將進一步探討純量類型。String!
表示該欄位為非空值,表示 GraphQL 服務保證在查詢此欄位時,一定會提供一個值。在類型語言中,我們會用驚嘆號表示這些值。[Episode!]!
表示一個 Episode
物件的陣列。由於它也是非空值,因此在查詢 appearsIn
欄位時,您一定會得到一個陣列(包含零個或多個項目)。而且由於 Episode!
也是非空值,因此您一定會得到一個陣列,其每個項目都是 Episode
物件。現在您知道 GraphQL 物件類型看起來像什麼,以及如何閱讀 GraphQL 類型語言的基本原理。
GraphQL 物件類型上的每個欄位可以有零個或多個引數,例如以下的 length
欄位
type Starship { id: ID! name: String! length(unit: LengthUnit = METER): Float}
所有引數都有名稱。與 JavaScript 和 Python 等語言不同,這些語言的功能會採用一個有序引數清單,GraphQL 中的所有引數都特別按名稱傳遞。在此情況下,length
欄位有一個已定義的引數,unit
。
引數可以是必需的或選擇性的。當一個引數是選擇性的,我們可以定義一個預設值 - 如果未傳遞 unit
引數,它會預設設定為 METER
。
架構中的大多數類型都只是正常的物件類型,但在架構中會有兩種特別的類型
schema { query: Query mutation: Mutation}
每個 GraphQL 服務都有 query
類型,可能會有或沒有 mutation
類型。這些類型與一般物件類型相同,但它們很特別,因為它們定義了每個 GraphQL 查詢的進入點。因此,如果您看到類似這樣的查詢
這表示 GraphQL 服務需要具有包含 hero
和 droid
欄位的 Query
類型
type Query { hero(episode: Episode): Character droid(id: ID!): Droid}
變異以類似方式運作 - 您在 Mutation
類型上定義欄位,這些欄位可用作您可以在查詢中呼叫的根變異欄位。
重要的是要記住,除了作為架構的「進入點」的特殊狀態外,Query
和 Mutation
類型與任何其他 GraphQL 物件類型相同,而且它們的欄位運作方式完全相同。
GraphQL 物件類型具有名稱和欄位,但這些欄位在某個時間點必須解析為具體資料。這就是純量類型發揮作用的地方:它們表示查詢的葉節點。
在下列查詢中,name
和 appearsIn
欄位將解析為純量類型
我們知道這一點,因為這些欄位沒有任何子欄位 - 它們是查詢的葉節點。
GraphQL 內建一組預設純量類型
Int
:有符號 32 位元整數。Float
:有符號雙精度浮點值。String
:UTF-8 字元序列。Boolean
:true
或 false
。ID
:ID 純量類型表示唯一識別碼,通常用於重新擷取物件或作為快取的鍵。ID 類型的序列化方式與字串相同;但是,將其定義為 ID
表示它不應供人類閱讀。在大部分 GraphQL 服務實作中,也有辦法指定自訂純量型別。例如,我們可以定義一個 Date
型別
scalar Date
然後,由我們的實作定義該型別應如何序列化、反序列化和驗證。例如,你可以指定 Date
型別應始終序列化為整數時間戳記,而你的用戶端應知道任何日期欄位都預期採用該格式。
列舉型別(也稱為 列舉)是一種特殊純量,限制為特定允許值集合。這可讓你
以下是 GraphQL 架構語言中列舉定義的範例
enum Episode { NEWHOPE EMPIRE JEDI}
這表示無論我們在架構中何處使用 Episode
型別,我們都預期它正好是 NEWHOPE
、EMPIRE
或 JEDI
之一。
請注意,各種語言中的 GraphQL 服務實作將有其特定語言處理列舉的方式。在支援列舉為一級公民的語言中,實作可能會利用該功能;在像 JavaScript 這樣不支援列舉的語言中,這些值可能會內部對應到一組整數。然而,這些詳細資料不會洩漏給用戶端,而用戶端可以完全根據列舉值的字串名稱進行操作。
物件型別、純量和列舉是你可以在 GraphQL 中定義的唯一型別種類。不過,當你在架構的其他部分或在查詢變數宣告中使用型別時,你可以套用影響這些值驗證的其他 型別修改器。我們來看一個範例
type Character { name: String! appearsIn: [Episode]!}
在此,我們使用 String
型別,並透過在型別名稱後加上驚嘆號 !
將其標示為 非 Null。這表示我們的伺服器始終預期為此欄位傳回非 Null 值,而如果最後取得 Null 值,實際上會觸發 GraphQL 執行錯誤,讓用戶端知道發生錯誤。
非 Null 類型修改器也可以在定義欄位引數時使用,如果在 GraphQL 字串或變數中傳遞 null 值作為該引數,GraphQL 伺服器將會回傳驗證錯誤。
清單以類似的方式運作:我們可以使用類型修改器將類型標記為 清單
,這表示此欄位將回傳該類型的陣列。在架構語言中,這表示為使用方括號 [
和 ]
包住類型。它對引數也有相同作用,驗證步驟會預期該值為陣列。
非 Null 和清單修改器可以結合使用。例如,您可以擁有非 Null 字串的清單
myField: [String!]
這表示清單本身可以為 null,但它不能有任何 null 成員。例如,在 JSON 中
myField: null // validmyField: [] // validmyField: ["a", "b"] // validmyField: ["a", null, "b"] // error
現在,假設我們定義了非 Null 字串清單
myField: [String]!
這表示清單本身不能為 null,但它可以包含 null 值
myField: null // errormyField: [] // validmyField: ["a", "b"] // validmyField: ["a", null, "b"] // valid
您可以根據需要任意巢狀任何數量的非 Null 和清單修改器。
與許多類型系統一樣,GraphQL 支援介面。介面是一種抽象類型,包含一組特定的欄位,類型必須包含這些欄位才能實作介面。
例如,您可以有一個介面 角色
,代表星際大戰三部曲中的任何角色
interface Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]!}
這表示任何實作 角色
的類型都需要有這些確切的欄位,以及這些引數和回傳類型。
例如,以下是可能實作 角色
的一些類型
type Human implements Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! starships: [Starship] totalCredits: Int}
type Droid implements Character { id: ID! name: String! friends: [Character] appearsIn: [Episode]! primaryFunction: String}
您可以看到這兩種類型都包含 角色
介面中的所有欄位,但也帶入了額外的欄位,總信用
、星艦
和 主要功能
,這些欄位特定於該特定類型的角色。
當您想要傳回一個物件或一組物件,但這些物件可能是多種不同類型時,介面會很有用。
例如,請注意以下查詢會產生錯誤
hero
欄位傳回類型 Character
,這表示它可能是 Human
或 Droid
,具體取決於 episode
參數。在上述查詢中,您只能要求存在於 Character
介面上的欄位,其中不包含 primaryFunction
。
若要要求特定物件類型上的欄位,您需要使用內嵌片段
在查詢指南的內嵌片段區段中進一步了解這一點。
聯合類型與介面有相似之處;但是,它們缺乏在組成類型之間定義任何共用欄位的能力。
union SearchResult = Human | Droid | Starship
無論我們在架構中傳回什麼 SearchResult
類型,我們都可能得到 Human
、Droid
或 Starship
。請注意,聯合類型的成員需要是具體物件類型;您無法從介面或其他聯合類型建立聯合類型。
在這種情況下,如果您查詢傳回 SearchResult
聯合類型的欄位,您需要使用內嵌片段才能查詢任何欄位
__typename
欄位會解析為 String
,讓您可以在客戶端區分不同的資料類型。
此外,在這種情況下,由於 Human
和 Droid
共用一個介面 (Character
),您可以查詢它們的共用欄位,而無需在多個類型中重複相同的欄位
{ search(text: "an") { __typename ... on Character { name } ... on Human { height } ... on Droid { primaryFunction } ... on Starship { name length } }}
請注意,name
仍指定在 Starship
上,否則它不會顯示在結果中,因為 Starship
不是 Character
!
到目前為止,我們只討論過將標量值(如列舉或字串)傳遞為欄位中的參數。但您也可以輕鬆傳遞複雜物件。這在變異的情況下特別有價值,您可能想要傳遞一個要建立的完整物件。在 GraphQL 架構語言中,輸入類型看起來與一般物件類型完全相同,但關鍵字為 input
,而不是 type
input ReviewInput { stars: Int! commentary: String}
以下是您如何在變異中使用輸入物件類型
輸入物件類型上的欄位本身可以參考輸入物件類型,但您無法在架構中混合輸入和輸出類型。輸入物件類型也不能在其欄位上有參數。