不同的分頁模式啟用不同的用戶端功能
在 GraphQL 中,一個常見的用例是遍歷物件集之間的關聯。有許多不同的方式可以在 GraphQL 中公開這些關聯,為用戶端開發人員提供不同的功能集。
公開物件之間關聯最簡單的方法是使用傳回複數類型的欄位。例如,如果我們想要取得 R2-D2 朋友的清單,我們可以要求取得所有朋友
然而,我們很快就會發現用戶端可能需要其他行為。用戶端可能希望能夠指定要擷取多少朋友;也許他們只想要前兩個。因此,我們希望公開類似這樣的內容
{ hero { name friends(first: 2) { name } }}
但是,如果我們只擷取前兩個,我們可能也希望對清單進行分頁;一旦用戶端擷取前兩個朋友,他們可能希望傳送第二個請求來要求下兩個朋友。我們如何啟用該行為?
有許多方法可以進行分頁
friends(first:2 offset:2)
的動作,來要求清單中的下兩個。friends(first:2 after:$friendId)
的動作,來要求我們擷取的最後一個朋友之後的兩個。friends(first:2 after:$friendCursor)
的動作,其中我們從最後一項取得游標並用來分頁。一般來說,我們發現基於游標的分頁是設計中最強大的。特別是如果游標是不透明的,則可以透過基於游標的分頁實作基於偏移量或 ID 的分頁(透過將游標設為偏移量或 ID),而且如果分頁模型在未來變更,使用游標會提供額外的彈性。為了提醒游標是不透明的,而且不應依賴其格式,我們建議對其進行 base64 編碼。
這會導致一個問題;但是,我們如何從物件取得游標?我們不希望游標存在於 User
類型中;它是連線的屬性,而非物件的屬性。因此我們可能想要引入一個新的間接層級;我們的 friends
欄位應提供邊緣清單,而邊緣同時具有游標和基礎節點
{ hero { name friends(first: 2) { edges { node { name } cursor } } }}
如果資訊是特定於邊緣,而非特定於其中一個物件,邊緣的概念也會很有用。例如,如果我們想要在 API 中公開「友誼時間」,讓它存在於邊緣是一個很自然的地方。
現在我們有能力使用游標對連線進行分頁,但是我們如何知道何時到達連線的尾端?我們必須持續查詢,直到我們取得一個空清單,但我們真的很希望連線在我們到達尾端時告訴我們,這樣我們就不需要額外的請求。類似地,如果我們想要知道關於連線本身的其他資訊;例如,R2-D2 總共有多少朋友?
為了解決這兩個問題,我們的 friends
欄位可以傳回一個連線物件。然後連線物件將有一個欄位用於邊緣,以及其他資訊(例如總計數和關於是否存在下一頁的資訊)。因此,我們的最後查詢可能看起來更像
{ hero { name friends(first: 2) { totalCount edges { node { name } cursor } pageInfo { endCursor hasNextPage } } }}
請注意,我們也可能在這個 PageInfo
物件中包含 endCursor
和 startCursor
。這樣一來,如果我們不需要邊緣包含的任何其他資訊,我們就完全不需要查詢邊緣,因為我們從 pageInfo
取得了分頁所需的游標。這會為連線帶來潛在的可使用性改善;我們不僅公開 edges
清單,我們還可以公開僅包含節點的專用清單,以避免間接層。
很明顯地,這比我們原本僅有一個複數的設計更為複雜!但是,透過採用這個設計,我們已經為客戶端解鎖了許多功能
totalCount
或 pageInfo
的能力。cursor
或 friendshipTime
的能力。若要實際操作,範例架構中有一個額外的欄位稱為 friendsConnection
,可顯示所有這些概念。您可以在範例查詢中查看。嘗試移除 friendsConnection
的 after
參數,看看分頁會如何受到影響。此外,嘗試在連線中將 edges
欄位替換為輔助的 friends
欄位,當這對客戶端合適時,讓您可以直接取得朋友清單,而不需要額外的邊緣間接層。
為了確保此模式的一致實作,Relay 專案有一個正式的 規格,您可以遵循此規格來建置使用基於游標的連線模式的 GraphQL API。