2 つの戦略があります: offset ベース (limit/offset) と cursor ベース です。GraphQL には組み込みのページネーションはありませんが、コミュニティの標準は Relay Connection 仕様で、cursor ページネーションを形式化しています。
2 つの戦略があります: offset ベース (limit/offset) と cursor ベース です。GraphQL には組み込みのページネーションはありませんが、コミュニティの標準は Relay Connection 仕様で、cursor ページネーションを形式化しています。
items(limit: 10, offset: 20)) はシンプルですが、ライブデータで破綻します。ユーザーがページを進めている間に行が挿入されると、要素がずれて重複や抜けが見えます。また深い offset では遅くなります (DB はスキップされる行を依然としてスキャンします)。connection はリストを edges (それぞれ cursor と node を持つ) と pageInfo でラップします:
type Query {
posts(first: Int!, after: String): PostConnection! # first = page size, after = cursor
}
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
}
type PostEdge {
cursor: String! # opaque token for THIS node's position
node: Post! # the actual item
}
type PageInfo {
endCursor: String # feed this back as 'after' for the next page
hasNextPage: Boolean! # so the client knows when to stop
}
// resolver: fetch ONE extra row to compute hasNextPage cheaply
const rows = await db.posts.find({ after: args.after, limit: args.first + 1 });
const hasNextPage = rows.length > args.first;
const page = rows.slice(0, args.first);
return {
edges: page.map((p) => ({ node: p, cursor: encodeCursor(p.id) })),
pageInfo: { endCursor: page.at(-1) && encodeCursor(page.at(-1).id), hasNextPage },
};
cursor は 不透明 です — 通常はソートキーの base64 — なので、クライアントはそれを数値ではなくトークンとして扱います。
offset と cursor の選択は、並行書き込み下での一貫性 と 深いページのパフォーマンス を理解していることを示します。Relay connection の形を知っていることが重要なのは、Apollo/Relay クライアントがそれ向けの組み込みキャッシュ処理を持っているからです。
ジュニアからシニアまで、詳細な回答付きのIT面接質問ライブラリ。
寄付する