Schemas and Types
認識 GraphQL 型別系統的各種元素
GraphQL 的型別系統描述了 API 可以查詢哪些資料。這些能力的集合稱為服務的 schema(綱要),前端可以利用這個 schema 向 API 發送查詢,並獲得可預期的結果。
本頁會帶你探索 GraphQL 的六種命名類型定義以及型別系統的其他功能,學習如何用它們描述資料與資料之間的關聯。由於 GraphQL 可以搭配任何後端框架或程式語言使用,這裡我們會避免討論實作細節,專注於概念說明。
型別系統
如果你看過 GraphQL 查詢語言,你會發現它基本上就是在物件上選取欄位。例如,以下這個查詢:
# { "graphiql": true }
{
hero {
name
appearsIn
}
}
- 我們從一個特殊的「根」物件開始
- 在這個物件上選取
hero欄位 - 對
hero回傳的物件,再選取name和appearsIn欄位
因為 GraphQL 查詢的結構和回傳結果非常接近,所以即使不太了解伺服器,也能預測查詢會得到什麼資料。不過,能夠明確描述可查詢的資料會更有幫助。例如:可以選哪些欄位?這些欄位會回傳什麼型別的物件?子物件又有哪些欄位?
這就是 schema 的用途。每個 GraphQL 服務都會定義一組型別,完整描述這個服務可以查詢的所有資料。當收到查詢請求時,系統會根據這個 schema 進行驗證與執行。
型別語言(Type language)
GraphQL 服務可以用任何語言實作,定義 schema 型別時也有多種方式:
- 有些函式庫會讓你用同一種程式語言同時定義 schema 型別、欄位與 resolver(解析函式)。
- 有些函式庫則提供所謂的 schema 定義語言(SDL),讓你用更直覺的方式定義型別與欄位,再分開撰寫 resolver。
- 有些函式庫甚至可以直接從 resolver 推斷出 schema。
- 也有函式庫能根據底層資料來源自動推斷型別與 resolver。
因為這份教學不侷限於特定語言,我們會用 SDL 來說明,因為它和查詢語言很像,也方便用語言無關的方式討論 GraphQL schema。
物件型別與欄位
GraphQL schema 最基本的組成是物件型別(Object types),它代表你可以從服務查詢到的某種物件,以及這個物件有哪些欄位。在 SDL 中可以這樣表示:
type Character {
name: String!
appearsIn: [Episode!]!
}
這段語法很直覺,但我們還是來說明一下:
Character是一個 GraphQL 物件型別,代表一種有欄位的型別。大多數 schema 裡的型別都是物件型別。name和appearsIn是Character型別的欄位。也就是說,只有這兩個欄位可以在查詢Character型別時出現。String是內建的純量型別(Scalar types),這種型別只能回傳單一純量值,不能再有子查詢。稍後會再介紹純量型別。String!表示這個欄位是非 Null 型別,也就是 GraphQL 服務保證查詢這個欄位時一定會有值。SDL 用驚嘆號表示非 Null。[Episode!]!表示這是清單型別(List type),裡面每個元素都是非 Null 的Episode物件,整個欄位本身也不能是 Null。
現在你已經知道 GraphQL 物件型別的基本結構,以及如何閱讀 SDL 了。
參數(Arguments)
每個 GraphQL 物件型別的欄位都可以有零個或多個參數,例如下方 length 欄位:
type Starship {
id: ID!
name: String!
length(unit: LengthUnit = METER): Float
}
所有參數都是具名的。和 JavaScript、Python 這類語言的函式會用順序傳參數不同,GraphQL 的參數都是用名稱對應。在這個例子中,length 欄位有一個名為 unit 的參數。
參數可以是必填或選填。當參數是選填時,可以定義 預設值。如果沒有傳入 unit 參數,則會自動設為 METER。
Query、Mutation 與 Subscription 型別
每個 GraphQL schema 都必須支援 query 操作。這個根操作型別的進入點,預設是一個名為 Query 的物件型別。所以如果你看到這樣的查詢:
# { "graphiql": true }
{
droid(id: "2000") {
name
}
}
代表 GraphQL 服務必須有一個 Query 型別,裡面有 droid 欄位:
type Query {
droid(id: ID!): Droid
}
Schema 也可以透過額外的 Mutation 和 Subscription 型別,支援 mutation 和 subscription 操作,並在這些根型別上定義欄位。
要記得,除了作為 schema 進入點的特殊地位外,Query、Mutation、Subscription 型別和其他 GraphQL 物件型別完全一樣,欄位運作方式也一樣。
你也可以自訂根操作型別的名稱,如果這麼做,必須用 schema 關鍵字告訴 GraphQL 新的名稱:
schema {
query: MyQueryType
mutation: MyMutationType
subscription: MySubscriptionType
}
純量型別(Scalar types)
GraphQL 物件型別有名稱和欄位,但最終這些欄位必須對應到具體的資料。這時就需要純量型別(Scalar types):它們代表查詢的葉節點值。
在下方查詢中,name 和 appearsIn 欄位會對應到純量型別:
# { "graphiql": true }
{
hero {
name
appearsIn
}
}
我們知道這些欄位是純量型別,因為它們沒有子欄位——它們是查詢的葉節點。
GraphQL 內建一組預設純量型別:
Int:有號 32 位元整數。Float:有號雙精度浮點數。String:UTF-8 字元序列。Boolean:true或false。ID:唯一識別碼,常用於重新查詢物件或作為快取鍵。ID型別序列化方式和String一樣,但語意上不是給人類閱讀的。
大多數 GraphQL 服務實作也支援自訂純量型別。例如可以定義 Date 型別:
scalar Date
接下來就要由你的實作決定這個型別如何序列化、反序列化與驗證。例如你可以規定 Date 型別一定要序列化成整數 timestamp,前端也要知道要用這種格式處理日期欄位。
列舉型別(Enum types)
列舉型別(Enum types)是一種特殊的純量型別,只允許特定的一組值。這有兩個好處:
- 可以驗證參數值是否在允許的範圍內
- 透過型別系統告訴前端,某個欄位只會是有限集合中的一個值
在 SDL 中,列舉型別的定義如下:
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
這代表只要 schema 中用到 Episode 型別,就只能是 NEWHOPE、EMPIRE 或 JEDI 其中之一。
不同語言的 GraphQL 服務實作會用各自的方式處理 Enum 型別。如果語言本身有 enum 支援,實作上會直接用 enum;像 JavaScript 沒有 enum,可能就會用一組整數對應。不過這些細節對前端來說是透明的,前端只需要用 Enum 型別的字串值即可。
型別修飾子(Type modifiers)
GraphQL 預設型別都是可為 null 且單一值。不過在 schema(或查詢變數宣告)中使用型別時,可以加上 型別修飾子 來改變欄位或參數的型別意義。
如前面物件型別的例子,GraphQL 支援兩種型別修飾子——清單型別(List)和非 Null 型別(Non-Null)