🤨

Reactでデータフェッチをする際にSuspenseを使おう!!

2023/12/08に公開

本記事で使用する技術

  • Reactv18~
  • TypeScript
  • 気象庁のお天気API

導入

みなさんはReactでデータフェッチを行い、その間にローディング画面を表示させるといったものを実装した経験あると思います。

例えば、下記のようなuseEffectを用いた実装方法が挙げられると思います。

useEffectを用いたデータ取得方法

App.tsx
import { useEffect, useState } from 'react'

const App = () => {

  const [weather, setWeather] = useState<any>(null)

  const getWeather = async () => {
    const res = await fetch(`https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json`)
    const weatherData = await res.json()
    setWeather(weatherData)
  }

  useEffect(() => {
    getWeather()
  }, [])

  return (
      <div>
        {weather ? (
          <div>
            <p>{weather[0].publishingOffice}</p>
          </div>
        ): (
          <p>loading...</p>
        )}
      </div>
  )
}

export default App

しかし、React18である現在、他にもさまざまな方法での実装が可能となっています。useEffectでのデータフェッチを行っているという方はぜひ見ていただけたら嬉しいです。

Suspenseへの招待

SuspenseとはReactが提供しているコンポーネントで、データフェッチを待ってくれるものです。

Suspenseで囲われているExampleのデータが取得できるまでは、fallback内がレンダリングされ、完了するとExampleがレンダリングされます。

<Suspense fallback={<p>Loading...</p>}>
  <Exmaple />
</Suspense>

どういう仕組みなのか

下記のコードを見てもらえればわかりますが、Promiseをthrowすることによって実現しています。

throwと聞くと反射でエラー処理を思い浮かべる方も多くいるかと思いますが、throwはPromiseに使うこともできます。

throwされたpending状態のPromiseはSuspenseによって捕まえられ、Suspenseはfallback内のものをレンダリングします。

データが取得できたあとは、if文により、Promiseはthrowされず、returnの中身がレンダリングされます。

イメージ図

Suspenseコード見本

App.tsx
import { Suspense } from 'react';

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

let weatherData: any; 

const getWeatherData = async (): Promise<any> => {
  //データ取得がわかりやすいように5秒待つ
  await sleep(5000);
  const res = await fetch(`https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json`)
  return res.json();
}

const SuspendWeather = () => {

  if(!weatherData){
    throw getWeatherData().then((data) => (weatherData = data))
  }

  return (
    <div>
      <p>{weatherData[0].publishingOffice}</p>
    </div>
  );
}

const App = () => {

  return (
    <>
      <Suspense fallback={<p>Loading...</p>}>
        <SuspendWeather />
      </Suspense>
    </>
  );
}

export default App;

SWRを使った方法

SWRとは、vercel社が提供するデータ取得関連を簡単に行うことのできるReactのライブラリです。

npm install swr 

今回のメインはSuspenseですし、特に説明なしでも理解できると思うので、下記のコードをご覧ください。

App.tsx
import { Suspense } from 'react';
import useSWR from 'swr';

const SuspendWeather = () => {

     const fetcher = async(url: string) => {
       //データ取得がわかりやすいように5秒待つ
        await sleep(5000);
        const res = await fetch(url)
        return res.json()
    }

    const { data, error, isLoading } = useSWR(`https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json`, fetcher)

    if (isLoading) {
        return (
            <div>
                <p>Loading...</p>
            </div>
        )
    }

    return (
        <div>
            <p>{data[0].publishingOffice}</p>
        </div>
    );
}

const Home = () => {

    return (
        <>
            <SuspendWeather />
        </>
    ); 
}

export default Home;

SWR + Suspenseを使った方法

また、SWRにSuspenseを組み合わせた実装を行うことも可能です。useSWRの第三引数に{suspense: true}を指定するだけです。

throwPromiseといったものを記述する必要もなく、前節の理解がなくても、天下り的に利用することができます。

App.tsx
import { Suspense } from 'react';
import useSWR from 'swr';

const SuspendWeather = () => {

     const fetcher = async(url: string) => {
       //データ取得がわかりやすいように5秒待つ
        await sleep(5000);
        const res = await fetch(url)
        return res.json()
    }

    const { data, error } = useSWR(`https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json`, fetcher, { suspense: true })

    return (
        <div>
            <p>{data[0].publishingOffice}</p>
        </div>
    );
}

const Home = () => {

    return (
        <>
            <Suspense fallback={<p>Loading...</p>}>
                <SuspendWeather />
            </Suspense>
        </>
    );
}

export default Home;

まとめ

最近のフロントエンド界隈のスピードは早く、この前推奨されていたものが、もう非推奨になったり、新しいフレームワークがどんどんでてきたり、追いついていくのに必死だと思います。私もその一人です。今回の記事も全く完璧なものではありませんが、役に立つものであったら嬉しいです。
また、間違いっている部分があったら、指摘いただけら幸いです。

Discussion