React Suspence を使ってみた!

記事の概要

こんにちは、ネットオンでフロントエンジニアをやっているサイトーさんです。

フロントエンドの開発では React などの技術がどんどん新しくなっていき、追いかけるのが大変だなと日々感じています。
そんな中、流行りの技術に乗り遅れないように今回は React の Suspence の使い方について勉強してみました。

目的

  • React Suspence 機能を使って簡単なTodoアプリを作ってみる!
  • Suspence があると何が嬉しいのか解説します
  • 今後の自分のためのメモになるように。。。

この記事はこういう方にオススメ

  • React の新機能を使ってみたい方
  • Suspence がどんな機能か知りたい方
  • フロントエンドに興味がある方

Suspenceを使ってみる

今回使用するもの

この記事では以下のものを使用します。

  • React (使用するバージョンは 18.1)
  • Next.js (使用するバージョンは 12.1)
  • TypeScript

下準備

今回はSSRの機能を使用しないため、 Next.js のカスタムApp を用いて以下のようにコンポーネントをSPAとして呼び出します。

function MyApp({ Component, pageProps }: AppProps) {
  const SafeHydrate = dynamic(() => import('../src/component/SafeHydrate'), { ssr: false });
  return (
  <SafeHydrate >
    <Component {...pageProps} />
  </SafeHydrate>)
}

export default MyApp

また、Todoアプリのデータとして、Next.js の API Routesで以下の API を定義します。

type Todo = {
  id: number
  date: string
  title: string
  description?: string
  status: 'done' | 'notYet'
}

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse<Todo[]>
) {
  await setTimeout(() => {
    res.status(200).json([
      {id: 0, date: '2022/01/01', title: '今年の目標', description: 'React を使いこなす!', status: 'done'},
      {id: 1, date: '2022/03/07', title: 'レポートを書く!', status: 'done'},
      {id: 2, date: '2022/05/05', title: 'ブログ記事を作る!', status: 'notYet'},
      {id: 3, date: '2022/12/31', title: 'Reactマスターになる!', description: 'なれるのかな?', status: 'notYet'}
    ])
  }, 1000);
}

Suspence 機能がわかりやすくなるように setTimeout を用いて1秒待つようにしています。

Suspence の使い方

そもそも Suspence とは表示したいコンポーネントをデータの準備中なのでレンダリングができないよという状態を作ることができるようになる機能です。
React はまだ描画ができないコンポーネントがあるとレンダリングの結果が DOM に反映できません。そこで Supence が描画できないコンポーネントの代わりに、別のコンポーネントを一時的に表示してくれることで部分的に画面表示することが可能になります。

そんな便利な Suspence を使うのはとても簡単です!
表示に時間のかかるコンポーネントを Suspence で囲むだけです。

const Home: NextPage = () => {
  return (
    <div>
      <h1>Todoりすと</h1>
      <Suspense fallback={<div>準備中...</div>}>
        <TodoList/>
      </Suspense>
    </div>
  );
};

export default Home;

Suspence で囲まれたコンポーネントはデータが準備中の場合は Suspence が fallback に渡されているものを代わりに表示してくれます。

とても簡単ですが、Suspence で囲まれたコンポーネントは Suspence に表示を待ってもうためにどうすればよいのでしょうか?

Supsence にローディング中と認識してもらうためには、 promise を throw する必要があります。

type Todo = {
  id: number
  date: string
  title: string
  description?: string
  status: 'done' | 'notYet'
}

let todoList: Todo[];
export const TodoList =  () => {
  const d = getTodoList()
  d.then((value) => {todoList = value})
  if(!todoList) {
    throw d
  }

  return (
    <ul>
      {todoList.map(v => (
        <li key={v.id} style={{marginBottom:'10px'}}>
          {v.title}<br/>
          {!!v.description && <>{v.description}<br/></>}
          期限:{v.date}
          {v.status === 'done' && <span style={{color:'red'}}>  完了!</span>}
        </li>))}
    </ul>)
}

function getTodoList(): Promise<Todo[]> {
  return fetch("http://localhost:3000/api/todo").then((res) => res.json());
}

ここでは、 getTodoList がペンディング中の場合、TodoList コンポーネントは getTodoList の Promise を throw します。

動かしてみた

実際に動かした結果は以下のようになります。

getTodoListからデータを取得完了するまでは Suspence の fallback に指定したものが表示されていることがわかります。

Suspence を使用することで、コンポーネントの中で準備中の時の処理を実装する必要がなくなり、表示したいものに集中することができます。ただ、コンポーネント内で発生したエラーなどは Error Boundary などで対応する必要があるので、注意が必要です。

まとめ

Todo アプリで簡単に Suspence 機能を使用してみました。Suspence 自体はとても簡単に使えるので、今後の開発などで使用することになってもとっつきやすいのかなと感じました。Suspence はコンポーネントの Try-Catch のような機能なのかなと思います。今回は Supence を取り上げましたが、 React 18 には automatic batching やstartTransition などまだまだ新機能があるので機会があれば、これらの機能も紹介できればな思います。

参考文献