ReactのApolloでページネーション構築した(でもメジャーバージョンアップしたら動かない)
ReactのApolloでページネーションを構築しました。
サンプルに従ってつくったのですが メジャーバージョンアップすると使えないコールバック関数が使われていました。そのコールバック関数を使わないようにするとカーソルベースでのページネーションができませんでした。メジャーバージョンアップする前にはたぶん変更になると思う。とりあえず2020/11/05現在での動くページネーションになりました。
- https://apollographql-jp.com/tutorial/queries/
- https://github.com/leighhalliday/apollo-pagination-demo
公式で非推奨の関数をつかっているなんてムカつく・・・
最新のサンプルはこちら
www.apollographql.com
ページネーションとは、
丁付け、ページ割りという意味の英単語。Webページに長い文章を掲載する際に、同じデザインの複数のページに分割し、各ページへのリンクを並べたものをこのように呼ぶ
http://e-words.jp/w/%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%8D%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3.html より引用
googleとかの検索結果の下にでるこれです。
ReactのApolleのクライアントでのページネーションは2種類のやり方があります。
- オフセットベース(Offset-based pagination)
- カーソルベース(Cursor-based pagination)
です。
オフセットベース(Offset-based pagination)
mysqlなどをやった事がある人ならこのSQLのようなをみた事があります。
select id ,title from table_name LIMIT 10 OFFSET 40
書式としては
SELECT カラム名, ... FROM テーブル名 LIMIT 行数 OFFSET 開始位置;
例えば OFFSET に 40 を指定した場合、最初から 40 番目までのデータを飛ばして 41 番目のデータから取得を行います
このページネーションは件数が少ない場合は特に問題ないのですが 100万件あるデータをページネーションした場合には
select id ,title from table_name LIMIT 10 OFFSET 1000000
サーバーサイド側では100万件を読み込んで飛ばしています。100万件程度ならそれほど問題にならないですが1000万、1億と増えると読み込みが遅くなります。
カーソルベース(Cursor-based pagination)
取得したデータにカーソルを割り当て取得する形になります
mysqlで表現するのはちょっと違うかもしれないのですが
select id ,title from table_name
をカーソル付きのtempデーブルにいれる
そのテンプテーブルに対して条件をつける事で取得するイメージです
1回目 select id ,title from table_name limit 3 2回目 select id ,title from table_name WHERE カーソル > cursor:3 limit 3 3回目 select id ,title from table_name WHERE カーソル > cursor:6 limit 3
件数が少ないとあまり恩恵はないのですが、件数がふえるとサーバー側の参照がへるそうです。
githubのapiのページネーションについて
現在勉強しているgithubのapiは, カーソルベース(Cursor-based pagination)を採用しています。 またカーソルの情報とデータを分離しています。
この太枠でこっている部分が1回のリクエストで取得するデータです。
実際のgraphqlは以下のとおりです
query searchRepositories($first: Int,$last: Int,$query: String!,$after:String,$before:String, ){ search(query: $query, type: REPOSITORY, first: $first, last: $last, after:$after,before:$before) { repositoryCount pageInfo{ startCursor endCursor hasNextPage hasPreviousPage } edges { cursor node { ... on Repository { id name } } } } }
変数(パラメーターみたいなものです。便宜上でqueryというパラメーターをつけていますが無視してください)
{ "first": 3 , "last": null, "query":"graphql", "after":null, "before":null }
結果
{ "data": { "search": { "repositoryCount": 89884, "pageInfo": { "startCursor": "Y3Vyc29yOjE=", "endCursor": "Y3Vyc29yOjM=", "hasNextPage": true, "hasPreviousPage": false }, "edges": [ { "cursor": "Y3Vyc29yOjE=", "node": { "id": "MDEwOlJlcG9zaXRvcnkzOTMzMjkxMw==", "name": "graphql" } }, { "cursor": "Y3Vyc29yOjI=", "node": { "id": "MDEwOlJlcG9zaXRvcnk0MTg4NzIxNQ==", "name": "graphql.github.io" } }, { "cursor": "Y3Vyc29yOjM=", "node": { "id": "MDEwOlJlcG9zaXRvcnkxMzc3MjQ0ODA=", "name": "graphql-engine" } } ] } } }
注目してほしい所は
になります
2ページ目を設定する場合は 変数のafterの部分を取得したデータのendCursor部分を設定します
{ "first": 3 , "last": null, "query":"graphql", "after":"Y3Vyc29yOjM=", "before":null }
「Y3Vyc29yOjM=」はbase64になっていてデコードすると「cursor:3」です つまり「cursor:3」より後の3件を取得するというパラメーターになります
取得した結果が以下です
{ "data": { "search": { "repositoryCount": 89884, "pageInfo": { "startCursor": "Y3Vyc29yOjQ=", "endCursor": "Y3Vyc29yOjY=", "hasNextPage": true, "hasPreviousPage": true }, "edges": [ { "cursor": "Y3Vyc29yOjQ=", "node": { "id": "MDEwOlJlcG9zaXRvcnkzODMwNzQyOA==", "name": "graphql-js" } }, { "cursor": "Y3Vyc29yOjU=", "node": { "id": "MDEwOlJlcG9zaXRvcnkxMTQzODY5NjI=", "name": "graphql" } }, { "cursor": "Y3Vyc29yOjY=", "node": { "id": "MDEwOlJlcG9zaXRvcnkxMTMzNjE5MDY=", "name": "graphql" } } ] } } }
ここで注目なのは
"startCursor": "Y3Vyc29yOjQ=", "endCursor": "Y3Vyc29yOjY=",
ですこの部分をbase64でdecodeすると
"startCursor": "cursor:4", "endCursor": "cursor:6",
になります
https://tool-taro.com/base64_decode/ で確認
2ページに行った状態で1ページに戻りたい場合は以下の変数(パラメーター)になります
{ "first": null , "last": 3, "query":"graphql", "after":null, "before":"Y3Vyc29yOjQ=" }
注目する所は
beforeの部分に "startCursor": "Y3Vyc29yOjQ="(cursor:4)を設定して
lastに取得したい件数を設定する所です。
react apolloのページネーションについて
実際のソース
解説1:クエリー部分
query searchRepositories($first: Int,$last: Int,$query: String!,$after:String,$before:String, ){ search(query: $query, type: REPOSITORY, first: $first, last: $last, after:$after,before:$before) { repositoryCount
この部分で上記の変数(パラメーター)を設定しています
解説2:実際に取得する部分
const {loading, error, data, fetchMore} = useQuery(GET_REPOSITORIES, {variables: { first: 10 , last:null, query:props.query, after:null, before:null }, }, {fetchPolicy: "cache-and-network",} );
クエリーを実行すると以下の変数に値(オブジェクト)がはいります
loading, error, data, fetchMore
ページネーションで重要なのはfetchMoreになります。
fetchMoreは関数です。それを実行すると再取得が可能です。
fetchMore({ variables: { first: 10 , last:null, query:props.query, after: endCursor, before:null }, updateQuery: (prevResult, { fetchMoreResult }) => { return fetchMoreResult; }, })
fetchMoreに次のページを取得するための情報をセットします、次のページに行く場合には afterにendCursor、firstに10を設定しています。
逆に前のページに行くボタンの部分では afterにstartCursor、lastに10を設定しています。
悲劇:fetchMore関数のコールバック関数が使えなくなる・・・
次前のロジックができてよかった〜と思っていたら変なコンソールログがでました
The updateQuery callback for fetchMore is deprecated, and will be removed in the next major version of Apollo Client. Please convert updateQuery functions to field policies with appropriate read and merge functions, or use/adapt a helper function (such as concatPagination, offsetLimitPagination, or relayStylePagination) from @apollo/client/utilities. The field policy system handles pagination more effectively than a hand-written updateQuery function, and you only need to define the policy once, rather than every time you call fetchMore.
え?fetchMore関数のコールバックでupdateQueryを使うのは次のメージャーバージョンアップしたら動かない。。。 いやいや
他の人もハマっていた
調べてみたらInMemoryCacheの部分でオフセットみたいなものを設定するもよう・・・え?? それならはじめからオフセットベースの仕組みを作るべきじゃないの??
公式の記事を参考にしてInMemoryCacheを勉強するしかないみたい
ハマるならサーバー側をオブセットベースで作るようにする。
補足
この記事は「フロントエンドエンジニアのためのGraphQL with React 入門」 (https://www.udemy.com/course/graphql-with-react/) で学習したページネーション内容です。
この動画は、私みたいな初心者にとってわかりやすい動画でオススメです。 ただ「@apollo/client」に統合されていない時代の動画で公式のページネーションとは違いfetchMoreを使わない形になっています。
参考URL