25
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Next.jsAdvent Calendar 2019

Day 22

「zeit/swr」のpagination exampleでScroll Position Restoreを試す

Last updated at Posted at 2019-12-22

こちらはNext.js Advent Calendar 2019の22日目の記事です。

はじめに

例えばYoutubeをスマホで見ていたときに、↓のように一度ページ遷移をしてからブラウザバックしたときにスクロール位置が一番上まで戻ってしまった経験はないでしょうか。

cap1.gif

これはSPAが抱える課題の1つで、解決するためには「Scroll Position Restore」と呼ばれる「スクロール位置の保持」とその「リストア(復元)」が必要です。

また、それ以外にも「もっと読む」などで画面内に非同期で要素が追加された場合はその要素も合わせて復元しないと正しいスクロール位置には戻れません。これには複雑なStateの管理も必要になってきます。

このように「Scroll Position Restore」を実現するにはいくつか超えなくてはいけない壁があります。

zeit/swr の登場

2019年10月、 Next.jsやNowの開発元であるZeitから「zeit/swr」というライブラリが発表されました。
そして、この「zeit/swr」の機能の1つにある「Pagination」が「Scroll Position Restore」を実現するための機能となっています!

swr(Stale-While-Revalidate) とは主にキャッシュ周りの用語なのでこのライブラリもキャッシュ周りのハンドリングも含むdata fetchのReact Hooksライブラリとなっています。1

簡単にやってくれることを書くと、「最初にキャッシュした古いデータを見せておいて、その裏で最新のデータをfetchしてキャッシュと差し替える」といった感じかなと思います。2

早速、Paginationを動かしてみる

Next.jsもそうですが、リポジトリ内にexamplesというサンプル集があり、そこにPaginationのexampleもあったので今回はそれを動かしてみます。

swr/examples/pagination at master · zeit/swr

READMEに書いてあるとおり、Setupを終わらせます(このサンプル自体もNext.jsが使われていました)。

$ curl https://codeload.github.com/zeit/swr/tar.gz/master | tar -xz --strip=2 swr-master/examples/pagination
$ cd pagination
$ yarn
$ yarn dev

起動したサーバーにアクセスすると以下のページが出てきます。「load more」で読み込んだ要素が、ページ遷移をしてブラウザバックをしても表示されていることがわかると思います!また、スクロール位置も保持されていそうですね。3

cap2.gif

実装はこんな感じです。

import fetch from '../libs/fetch'
import Link from 'next/link'

import useSWR, { useSWRPages } from 'swr'

export default () => {
  const {
    pages,
    isLoadingMore,
    isReachingEnd,
    loadMore
  } = useSWRPages(
    // page key
    'demo-page',

    // page component
    ({ offset, withSWR }) => {
      const { data: projects } = withSWR(
        // use the wrapper to wrap the *pagination API SWR*
        useSWR('/api/projects?offset=' + (offset || 0), fetch)
      )
      // you can still use other SWRs outside

      if (!projects) {
        return <p>loading</p>
      }

      return projects.map(project => 
        <p key={project.id}>{project.name}</p>
      )
    },

    // one page's SWR => offset of next page
    ({ data: projects }) => {
      return projects && projects.length
        ? projects[projects.length - 1].id + 1
        : null
    },

    // deps of the page component
    []
  )

  return <div>
    <h1>Pagination (offset from data)</h1>
    {pages}
    <button onClick={loadMore} disabled={isReachingEnd || isLoadingMore}>
      {isLoadingMore ? '. . .' : isReachingEnd ? 'no more data' : 'load more'}
    </button>
    <hr />
    <Link href="/page-index"><a>page index based pagination </a></Link><br/>
    <Link href="/about"><a>go to another page </a></Link>
  </div>
}

useSWRPages()の引数として、キャッシュキーとしても使われるページ名、実際にfetchしたデータを使って生成するComponent、次ページのoffsetなどを設定しています。

ここの説明はコード側にシーケンス図があったのでそちらを見るとより分かりやすいかなと思います。

少し触ってみたりissueなども見たところ、まだスクロール位置の復元がやや不完全であったり、キャッシュのストアを触ろうとしたときに不自由さはあるものの、シンプルなアプリケーションであれば比較的簡単に利用できそうな印象でした。

さいごに

zeit/swrはまだまだ発表されたばかりで利用実績も多くありませんが、頻繁に開発されており、何より開発元がzeitであるため今後ますますNext.jsとうまく協調して進化していくはずです。Nextをお使いの方はぜひその動向に注目してみてください!

以上、22日目の記事でした!

  1. このライブラリきっかけでswr自体を勉強し直しました。。こちらの記事が分かりやすかったです。

  2. 詳しくはこちらの記事こちらの記事をおすすめします!

  3. 分かりやすくするために1度に読む込むデータ数を3件から10件に変更しています。

25
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?