在 Star Wars universe 中,一個 faction 有許多 ship。Relay 包含讓操作一對多關聯變得簡單的功能,使用一個標準化的方式來表達這些一對多關聯。這個標準的 connection 模型提供透過 connection 來處理 slice 和 paginate 的方法。
讓我們先選擇 rebels,並查詢它們的第一個 ship:
query RebelsShipsQuery { rebels { name, ships(first: 1) { edges { node { name } } } } }
產生
{ "rebels": { "name": "Alliance to Restore the Republic", "ships": { "edges": [ { "node": { "name": "X-Wing" } } ] } } }
使用 first
參數來把這對 ships
結果集切到只剩第一個。不過那如果我們想要用它做 paginate 呢?在每一個 edge 上,會有一個我們可以用來 paginate 的 cursor。這次讓我們查詢前兩個,並如法炮製的取回 cursor:
query MoreRebelShipsQuery { rebels { name, ships(first: 2) { edges { cursor node { name } } } } }
而我們取回
{ "rebels": { "name": "Alliance to Restore the Republic", "ships": { "edges": [ { "cursor": "YXJyYXljb25uZWN0aW9uOjA=", "node": { "name": "X-Wing" } }, { "cursor": "YXJyYXljb25uZWN0aW9uOjE=", "node": { "name": "Y-Wing" } } ] } } }
要注意,cursor 是一個 base64 字串。這是一個前面看過的模式:伺服器提醒我們這是一個不透明的字串。我們可以把這個字串傳回伺服器作為 ships
欄位的 after
參數,這或讓我們查詢在前一次結果的最後一筆的下三個 ship:
query EndOfRebelShipsQuery { rebels { name, ships(first: 3 after: "YXJyYXljb25uZWN0aW9uOjE=") { edges { cursor, node { name } } } } }
給我們
{ "rebels": { "name": "Alliance to Restore the Republic", "ships": { "edges": [ { "cursor": "YXJyYXljb25uZWN0aW9uOjI=", "node": { "name": "A-Wing" } }, { "cursor": "YXJyYXljb25uZWN0aW9uOjM=", "node": { "name": "Millenium Falcon" } }, { "cursor": "YXJyYXljb25uZWN0aW9uOjQ=", "node": { "name": "Home One" } } ] } } }
太棒了!讓我們繼續前進並取得後四個!
query RebelsQuery { rebels { name, ships(first: 4 after: "YXJyYXljb25uZWN0aW9uOjQ=") { edges { cursor, node { name } } } } }
產生
{ "rebels": { "name": "Alliance to Restore the Republic", "ships": { "edges": [] } } }
嗯。沒有 ship;猜測應該是在系統中 rebel 只有五個 ship。如果能知道我們已經碰到 connection 的末端的話很不錯,就不需要再一次往返來驗證這件事。connection 模型用一個叫做 PageInfo
的 type 來使用這個功能。因此讓我們發送兩個 query 再一次的幫我們把 ships 取回,但這次要查詢 hasNextPage
:
query EndOfRebelShipsQuery { rebels { name, originalShips: ships(first: 2) { edges { node { name } } pageInfo { hasNextPage } } moreShips: ships(first: 3 after: "YXJyYXljb25uZWN0aW9uOjE=") { edges { node { name } } pageInfo { hasNextPage } } } }
而我們取回
{ "rebels": { "name": "Alliance to Restore the Republic", "originalShips": { "edges": [ { "node": { "name": "X-Wing" } }, { "node": { "name": "Y-Wing" } } ], "pageInfo": { "hasNextPage": true } }, "moreShips": { "edges": [ { "node": { "name": "A-Wing" } }, { "node": { "name": "Millenium Falcon" } }, { "node": { "name": "Home One" } } ], "pageInfo": { "hasNextPage": false } } } }
因此在第一次對 ships 的 query,GraphQL 告訴我們有下一個 page,但是在下一次,它告訴我們已經碰到 connection 的末端。
Relay 使用這所有的功能來打造 connection 相關的抽象,讓這些能有效的運作而不需要在客戶端上手動的管理 cursor。
關於伺服器應該有怎樣的行為的完整細節可以在 GraphQL Cursor Connections spec 找到。