Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kenmori/740d5956c313daf78b7f90c5caa87894 to your computer and use it in GitHub Desktop.
Save kenmori/740d5956c313daf78b7f90c5caa87894 to your computer and use it in GitHub Desktop.
Apollo x Relay-Style-Cursor-Pagination(リレースタイルカーソルページネーション)

Apollo x Relay-Style-Cursor-Pagination(リレースタイルカーソルページネーション)

この記事は「GraphQL with React入門 - QueryとMutationを学びPaginationの実装にチャレンジ!」(https://www.udemy.com/graphql-with-react/) で学んだ知見をまとめたものです。

自分がapollo x graphQLを使うにあたって非常に役立った動画ですので。初学者におすすめです。

Apollo x Relay-Style-Cursor-Pagination(リレースタイルカーソルページネーション)

とはGithubにも実装されているユーザーが検索した際のページネーションの実装スタイル

登場人物

  • Connection ・・・ ある塊単位でデータを要求するその「ある塊」のこと
  • Page ・・・ Connectionが示す情報の集まり。PageedgespageInfoの2つで構成されている。

Pageの中身のEdgesとpageinfo

  • Edge・・・ 位置情報を示すCursorと情報そのものの node(データを抽象化した呼び名)で構成されている。
  • pageinfo・・・startCursorendCursorhasPreviousPagehasNextPageで構成されている
    • startCursor・・・ 先頭のエッジがもつCursor情報(位置情報)
    • endCursor・・・最後尾のエッジがもつCursor情報
    • hasPreviousPage・・・前のPageが存在するかどうか
    • hasNextPage・・・次のPageが存在するかどうか

case1 1ページ目を要求する場合

{
  quer: "Query とは"//検索文字列
  first: 5 //5件分のデータをください
}
// このpageの構成情報
{
  endCursor: "Y3hogehoge=",
  hasNextPage: true, //nextボタンの要否に使う
  hasPreviousPage: false,
  startCursor: "Y3eee="
}
### query
query searchRepogitriys($after: String, $before: String, $first: Int, $last: Int, $query: String!){
  search(after: $after, before: $before, first: $first, last: $last, query: $query, type:REPOSITORY) {
    repositoryCount
    pageInfo {
      endCursor
      hasNextPage
      hasPreviousPage
      startCursor
    }
  }
}
### variables
{
  "after": null,
  "before": null,
  "first": 5,
  "last": null,
  "query": "フロントエンドエンジニア"
}

### return data
{
  "data": {
    "search": {
      "repositoryCount": 22, //total22件のnodeがバックエンド側に存在するの意味
      "pageInfo": {
        "endCursor": "Y3Vyc29yOjU=",
        "hasNextPage": true, //totalが6件以上あるのでtrue
        "hasPreviousPage": false,
        "startCursor": "Y3Vyc29yOjE="
      }
    }
  }
}

case 2 次のページを要求する場合

以前の結果のafter値をendCorsorに指定して取得する

{
  quer: "Query とは"//検索文字列
  first: 5 //5件分のデータをください
  after: "Y3hogehoge=" //カーソル情報。case1で実行で得られたendCursorの値を入れる。「ここより後ろのデータを要求します」の意味
  before: null
  last: null
  
}
// このpageの構成情報
{
  endCursor: "Y3ggggg=",
  hasNextPage: true,
  hasPreviousPage: true,
  startCursor: "Y3aaaa="
}

case3 前のページを要求する場合

{
  quer: "Query とは"//検索文字列
  first: null
  after: null
  before: "Y3aaaa=" //前の結果で得られたstartcorsorを指定する。「ここより前の結果から5件分ください」の意味
  last: 5 //方向が逆なので
}
//return data
{
  endCursor: "Y3ggggg=",
  hasNextPage: true, //前のページに遡っているので必ずここはtrueになる
  hasPreviousPage: false,//もしも1ページ目ならfalse、 もしまだ遡れたらtrue
  startCursor: "Y3bbbb="
}

edges

query searchRepogitriys($after: String, $before: String, $first: Int, $last: Int, $query: String!){
 search(after: $after, before: $before, first: $first, last: $last, query: $query, type:REPOSITORY) {
   repositoryCount
   pageInfo {
     endCursor
     hasNextPage
     hasPreviousPage
     startCursor
   }
   edges { ## Pageとして要求した件数分エッジデータ
   	cursor ## エッジが持つ位置情報
     node { ## 情報そのもの
      ...on Repository {
       	id
       	url
       	name
     	}
     }
   }
 }
}


### return data

{
 "data": {
   "search": {
     "repositoryCount": 22,
     "pageInfo": {
       "endCursor": "Y3Vyc29yOjU=",
       "hasNextPage": true,
       "hasPreviousPage": false,
       "startCursor": "Y3Vyc29yOjE="
     },
     "edges": [
       {
         "cursor": "Y3Vyc29yOjE=",
         "node": { 
           "id": "MDEwOlJlcG9zaXRvcnkxMjM5NTA3MjU=",
           "url": "https://github.com/gipcompany/udemy-react-redux-crud-application",
           "name": "udemy-react-redux-crud-application"
         }
       },
       {
         "cursor": "Y3Vyc29yOjI=",
         "node": {
           "id": "MDEwOlJlcG9zaXRvcnkxNjU4Mjg4OTE=",
           "url": "https://github.com/a-oku-sharing-tech/frontend-work-space",
           "name": "frontend-work-space"
         }
       },
       {
         "cursor": "Y3Vyc29yOjM=",
         "node": {
           "id": "MDEwOlJlcG9zaXRvcnkxMTI5MjQwOTk=",
           "url": "https://github.com/kuro-channel/MyProfile",
           "name": "MyProfile"
         }
       },
       {
         "cursor": "Y3Vyc29yOjQ=",
         "node": {
           "id": "MDEwOlJlcG9zaXRvcnkxMzMwNjMxOTE=",
           "url": "https://github.com/ProgrammingSamurai/react-recipes",
           "name": "react-recipes"
         }
       },
       {
         "cursor": "Y3Vyc29yOjU=",
         "node": {
           "id": "MDEwOlJlcG9zaXRvcnk4MDgwMzYyOQ==",
           "url": "https://github.com/wakamor/fe-colosseum",
           "name": "fe-colosseum"
         }
       }
     ]
   }
 }
}

各edgeが持つcorsor情報をデコードしてみる

const corsors = [
 "Y3Vyc29yOjE=",
 "Y3Vyc29yOjI=",
 "Y3Vyc29yOjM=",
 "Y3Vyc29yOjQ=",
 "Y3Vyc29yOjU="
];

const result = corsors.map(e => {
 return Buffer.from(e, "Base64").toString("binary");
});
console.log(result);

https://codesandbox.io/s/practical-ishizaka-wimn1?fontsize=14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment