查詢(Queries)
學習如何從 GraphQL 伺服器取得資料
GraphQL 支援三種主要的操作類型——查詢(queries)、變更(mutations)以及訂閱(subscriptions)。我們在本指南中已經看過幾個基本查詢的範例,這一頁會詳細介紹如何使用查詢操作來從伺服器讀取資料。
欄位(Fields)
在最簡單的情況下,GraphQL 就是針對物件要求特定的欄位。我們先來看看在 schema 中 Query 型別所定義的 hero 欄位:
type Query {
hero: Character
}
我們可以看看查詢這個欄位時會得到什麼結果:
# { "graphiql": true }
{
hero {
name
}
}
當我們建立一個 GraphQL 文件 時,總是從根操作型別(本例中為 Query 物件型別)開始,因為它是 API 的進入點。接著我們必須指定我們感興趣的欄位 選擇集,一路往下到最底層的值,這些值會是 Scalar 或 Enum 型別。name 欄位回傳的是 String 型別,在這個例子中是星際大戰主角的名字,"R2-D2"。
GraphQL 規範指出,請求的結果會回傳在回應的頂層 data key 下。如果請求發生錯誤,會在頂層的 errors key 提供錯誤資訊。你可以發現,結果的結構和查詢的結構相同。這是 GraphQL 的核心,因為你總是能得到你預期的資料,伺服器也能精確知道客戶端要求哪些欄位。
在前面的例子中,我們只要求了 hero 的名字,這會回傳一個 String,但欄位也可以回傳物件型別(或其陣列)。這時你可以對該物件型別做 子選擇:
# { "graphiql": true }
{
hero {
name
friends {
name
}
}
}
GraphQL 查詢可以遍歷相關物件及其欄位,讓客戶端能在一次請求中取得大量相關資料,而不需要像傳統 REST 架構那樣多次往返請求。
請注意,在這個例子中,friends 欄位回傳的是一個項目陣列。GraphQL 查詢在單一項目或多項目時語法相同;不過我們可以根據 schema 的定義來判斷預期會拿到哪一種。
參數(Arguments)
如果我們只能遍歷物件及其欄位,GraphQL 已經是一個非常實用的資料查詢語言了。但當你可以為欄位傳遞參數時,事情就變得更有趣了:
type Query {
human(id: ID!): Human
}
客戶端必須在查詢時提供必要的 id 值:
# { "graphiql": true }
{
human(id: "1000") {
name
height
}
}
在像 REST 這樣的系統中,你只能傳遞一組參數——也就是請求中的查詢參數和 URL 片段。但在 GraphQL 中,每個欄位和巢狀物件都可以有自己的參數,這讓 GraphQL 完全可以取代多次 API 請求。
你甚至可以為回傳 Scalar 型別的欄位傳遞參數;一個常見用途是讓伺服器端統一處理資料轉換,而不是每個客戶端都要自己處理:
# { "graphiql": true }
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
參數可以是多種不同的型別。在上面的例子中,我們使用了 Enum 型別,代表有限選項之一(這裡是長度單位,可以是 METER 或 FOOT)。GraphQL 內建一組預設型別,但 GraphQL 伺服器也可以宣告自訂型別,只要能序列化成你的傳輸格式即可。
操作型別與名稱(Operation type and name)
在上面的範例中,我們使用了一種簡寫語法,省略了在操作選擇集前面的 query 關鍵字。除了可以明確指定 操作型別 之外,我們也可以為操作加上一個獨特的 操作名稱,這在正式環境的應用程式中特別有用,因為這樣有助於除錯與追蹤。
以下是一個同時包含 query 關鍵字(操作型別)與 HeroNameAndFriends(操作名稱)的範例:
# { "graphiql": true }
query HeroNameAndFriends {
hero {
name
friends {
name
}
}
}
操作型別可以是 query、mutation 或 subscription,用來描述你想執行的操作類別。這個關鍵字在查詢時可以省略(只限查詢),但在變更與訂閱時則是必須的。如果你想為操作命名,也必須明確指定操作型別。
操作名稱是你為操作自訂的名稱,建議取一個有意義的名字。當你在一份文件中傳送多個操作時,操作名稱是必須的;即使只傳送一個操作,也建議加上名稱,因為這對除錯與伺服器端日誌紀錄都很有幫助。當出現錯誤(不論是在網路日誌還是 GraphQL 伺服器的日誌中),你可以更容易地根據名稱在程式碼中找到對應的查詢,而不用去猜查詢內容。
別名(Aliases)
如果你很細心,可能已經注意到,因為回傳結果的欄位名稱會和查詢中的欄位名稱相同,但不包含參數,所以你無法直接用不同參數查詢同一個欄位。這時你就需要別名——它可以讓你把欄位的回傳結果重新命名成你想要的名稱。
# { "graphiql": true }
query {
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
在上面的例子中,兩個 hero 欄位本來會衝突,但因為我們可以用別名把它們改成不同的名稱,就能在同一次請求中取得兩個結果。
變數(Variables)
到目前為止,我們都是直接在查詢字串中寫死所有參數。但在大多數應用程式中,欄位的參數通常是動態的。例如,可能有一個下拉選單讓你選擇想看的星際大戰集數,或是一個搜尋欄位,或是一組篩選條件。
如果把這些動態參數直接寫在查詢字串裡,客戶端程式就必須在執行時動態操作查詢字串,並序列化成 GraphQL 特有的格式。為了解決這個問題,GraphQL 提供了第一級的變數支援,可以把動態值從查詢中抽離,另外用一個物件傳遞。這些值就叫做變數。
當我們開始使用變數時,需要做三件事:
- 把查詢裡的靜態值換成
$變數名稱 - 在查詢中宣告
$變數名稱為可接受的變數 - 在另外一個(通常是 JSON)變數物件裡傳入
變數名稱: 值
全部加起來會像這樣:
# { "graphiql": true, "variables": { "episode": "JEDI" } }
query HeroNameAndFriends($episode: Episode) {
hero(episode: $episode) {
name
friends {
name
}
}
}
你必須在 GraphQL 文件中指定操作型別與名稱,才能使用變數。
現在,在客戶端程式碼中,我們只需要傳入不同的變數值,而不用每次都組一個新的查詢字串。一般來說,這也是一個很好的習慣,可以明確標示哪些查詢參數是動態的——我們永遠不應該用字串插值的方式,把使用者輸入直接拼進查詢字串裡。
變數定義(Variable definitions)
變數定義就是像上面查詢裡 ($episode: Episode) 這一段。它的用法就像型別語言中函式的參數定義一樣。它會列出所有變數,前面加上 $,後面接型別,例如這裡的 Episode。
所有宣告的變數必須是 Scalar、Enum 或 Input Object 型別。所以如果你想傳遞複雜物件給欄位,必須知道伺服器上對應的 input type。
變數定義可以是選填或必填。像上面例子,因為 Episode 型別後面沒有 !,所以是選填。如果你要把變數傳給一個必填參數,那變數本身也必須是必填。
想更了解這些變數定義的語法,可以參考 Schemas and Types 頁面。
預設變數值(Default variables)
你也可以在查詢裡為變數加上預設值,只要在型別宣告後面加上預設值即可:
query HeroNameAndFriends($episode: Episode = JEDI) {
hero(episode: $episode) {
name
friends {
name
}
}
}
當所有變數都有預設值時,你可以不帶任何變數就呼叫查詢。如果有傳變數,則會覆蓋預設值。
片段(Fragments)
假設我們的應用程式有一個比較複雜的頁面,可以讓我們同時比較兩個英雄以及他們的朋友。你可以想像,這樣的查詢很快就會變得很冗長,因為至少要重複一次欄位——每一邊都要寫一次。
這就是為什麼 GraphQL 有可重複使用的單位,叫做片段(fragments)。片段讓你可以先定義一組欄位,然後在需要的查詢裡引用。以下是一個 用片段解決上述情境的範例:
# { "graphiql": true }
query {
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
你可以想像,如果不能用片段,這個查詢會有多冗長。片段的概念常被用來把複雜的資料需求拆成小區塊,特別是當你需要把很多 UI 元件的片段組合成一次初始資料請求時。