Skip to content

vercelといえばNext.jsが有名です。
https://nextjs.org/
Reactを使う人にとって、そして特にフロントエンド専門のエンジニアにとっては悩みの種だったサーバー側との連携の仕方について
「ちょうどよい」規模と多角アプローチで解決してくれる、言わずと知れた小さなフレームワークです。
また、同社が提供するvercelを使用することで、AWSを使用した簡易的なAPIを抽象的に実装できることで、小さなアプリならNext.js一つで完結させることもできるようになっています。

ですが今回はこのNext.jsではなく、私の周りのプログラマでも意外と知らない人が多かった同社の開発する「SWR」について触れたいと思います。
https://swr.vercel.app/

SWRとはなにか

SWRは、クライアント側のデータ取得を、Reactで状態管理しやすいようにしてくれるReact Hooksとそれを内包するライブラリです。

SWRを使わない実装

Next.jsでは、getServerSidePropsやgetStaticPropsなどのサーバーサイドで実行する関数を用意しており、データの取得をサーバー側で行い、Reactコンポーネントをhtmlとして生成した状態でブラウザに渡すことができます。

ですが、getServerSidePropsで複雑な処理をし過ぎると、当然レスポンスまでの時間が長くなり、ユーザー体験を著しく損なってしまいます。
そうなればクライアントサイドでデータフェッチを行うのは、Reactを使用しているならば寧ろ当たり前の処理となります。

例えば記事一覧を取得して→ログインしている状態であれば記事を表示、みたいな機能なら、丁寧に書くなら下記のようになるでしょう。

import type { GetServerSideProps } from 'next';
import Link from 'next/link';
import * as React from 'react';
import { fetchPosts } from '../../_lib/posts';

interface P {
  posts: {
    slug: string
    title: string
      content: string
  }[]
}

const Home: React.FC<P> = ({ posts }) => {
  const [isLoggedIn, setIsLoggedIn] = React.useState<boolean>(false);
  React.useEffect( () => {
    fetch('/api/user')
      .then((res) => {
        const user = await res.json();
              setIsLoggedIn(!!user);
      });
  }, []);

  return (
    <div>
        {isLoggedIn ? (
            <>
          {posts.map((post) => {
            return (
              <div key={post.slug}>
                <Link href={`/posts/${post.slug}`}>
                  <a aria-label={post.title}>
                    {post.title}
                  </a>
                </Link>
              </div>
            );
          })}
            </>
            )
      : null}
      </div>
  );
};

export const getServerSideProps: GetServerSideProps = async ({}) => {
  const posts = await fetchPosts();
  return {
    props: {
      posts,
      },
  };
};

もちろん、axiosやsuperagentなどのfetchを代替するライブラリを使用するケースが多いと思うので、こんなに冗長になることは稀でしょう。
ですが、ここに

などを追加すると・・・

/** 一部抜粋 */
const [isLoggedIn, setIsLoggedIn] = React.useState<boolean | null>(false);
React.useEffect(() => {
  fetch('/api/user')
    .then((res) => {
      const user = res.json();
      /** userはboolean or null とする */
      if (typeof user !== undefined) {
        setIsLoggedIn(user);
      } else {
        setIsLoggedIn(false);
      }
        });
}, []);

などとなります。
例のわかりづらさはともかく、こうしたfetchしたデータの状態管理は、ビューのロジックを複雑にしてしまいます。

SWRの出番

まずは例として、上記の抜粋部分を同じことが出来るように実装してみます。

import type { GetServerSideProps } from 'next';
import Link from 'next/link';
import useSWR from 'swr';
import * as React from 'react';
import { fetchPosts } from '../../_lib/posts';

/** 省略 */
const Home: React.FC<P> = ({ posts }) => {
  /*
  const [isLoggedIn, setIsLoggedIn] = React.useState<boolean>(false);
  React.useEffect(() => {
    fetch('/api/user')
      .then((res) => {
        const user = res.json();
        setIsLoggedIn(!!user);
    });
  }, []);
  */
  async function fetcher(url: string): Promise<boolean | null> {
    const response = await fetch(url);
      return response.json();
  }
  const { data: isLoggedIn } = useSWR('/api/user', fetcher);

  return (
    /** 省略 */
  );
};

これだけ!とても簡単です!
ここでは、importしたuseSWR関数の

ここで特筆すべきは、このdataオブジェクトの状態は一貫していて、

ですので、データ取得中は「Loading...」を表示したいなどの場合は、このdataがundefinedの場合の処理を書けばOKです。

SWRの優秀なところ

データ取得における状態管理

上述の通りのため割愛。とにかく状態管理がしやすいです。

データの再フェッチ

SWRはポーリングによるデータの自動再フェッチを行います。
取得するデータが変更された場合に、明示的に関数を再実行することなくデータの更新を行うことが出来ます。
※実行環境によっては再フェッチされない場合もあります。詳細は公式ドキュメントを参照。

データのキャッシュ

SWRで取得したデータは、第一引数で渡した文字列をキーとしてデータをキャッシュします。
キャッシュの中から古いデータを取得し、その後にfetch関数を実行して新しいデータを取得します。
これにより、

気の利いたオプション群

SWRはシンプルで機能こそ限られたライブラリですが、実行時のオプションや関数の戻り値一つ一つにとても気が利いています。
その一例を紹介します。

isValidating

const { data, isValidating } = useSWR('/api/user', fetcher);
/** isValidating: boolean */

データのリクエスト中、または再フェッチの場合にtrueになります。

error

const { data, error } = useSWR('/api/user', fetcher);

errorオブジェクトを取得できます。
errorの場合の表示の切り替えなども、ここで実装できます。

revalidateOnFocus

useSWR('/api/user', fetcher, { revalidateOnFocus: true });

ブラウザのウィンドウがフォーカスされたときに自動で再フェッチします。

revalidateOnReconnect

useSWR('/api/user', fetcher, { revalidateOnReconnect: true });

ネットワーク接続が切れて、回復したときに自動的に再フェッチします。

以上は一例です。
詳細なオプション群は下記ですべて確認できます。
https://swr.vercel.app/docs/options


最後に

Reactでのデータ取得→状態管理の流れは、実装を間違えると複雑になりがちです。
また、アプリが巨大化してくるほど、そのロジックの一部を変更するだけでも一苦労になります。
データを取得するバックエンドやAPIのエンドポイントさえ決まっていれば、どのプロジェクトのどのコンポーネントでも使用できるのがうれしいですね。

自前だと複雑な実装がスッキリとするので、一部だけでも書き換えて試しに使ってみてください。