いろいろ試してみた！Reactでのローディング画面実装方法

## はじめに
業務で、データを取得している間は画面を見せないようにするローディング画面を作る事があったので、備忘のためにその方法をまとめていきます。

### 環境
以下のものを使っています。
| ライブラリ | バージョン | 
|:-|-:|
| React | 18.2.0 |
| TypeScript | 5.2.2 |
| Vite | 5.2.0 |

## useStateをつかったローディング画面
やろうと思ったときにパッと思い浮かぶのはこれかなと思います。
そんなに難しいことはないので、早速コードを見ていきましょう。

```react:StateLoading.tsx
import { useEffect, useState } from "react";

// API用URL
const url =
  "https://api.open-meteo.com/v1/forecast?latitude=35.6895&longitude=139.6917&hourly=temperature_2m&timezone=Asia%2FTokyo";

// APIレスポンスの型定義
interface WeatherData {
  hourly: {
    temperature_2m: number[];
    time: string[];
  };
}

const StateLoading = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState<WeatherData>();

  // APIデータ取得用処理
  const fetchData = async () => {
    try {
      const response = await fetch(url);
      const result = await response.json();
      setData(result);
    } catch (e) {
      console.log(e);
    } finally {
      setIsLoading(false);
    }
  };

  // コンポーネント読み込み時に取得
  useEffect(() => {
    fetchData();
  }, []);

  if (isLoading) {
  // ロード中の場合はローディング画面
    return (
      <>
        <h1>Loading...</h1>
      </>
    );
  } else {
  // ロードが終わるとデータ表示
    return (
      <>
        <h2>Temperature Forecast</h2>
        {data && data.hourly && (
          <ul>
            {data.hourly.time.map((time, index) => (
              <li key={time}>
                {time}: {data.hourly.temperature_2m[index]}°C
              </li>
            ))}
          </ul>
        )}
      </>
    );
  }
};

export default StateLoading;
```

今回は天気を取得できるAPIを使っています。
ポイントは以下です。

### stateを使って分岐する
`isLoading`という`state`を用意しました。
この値が`true`の場合には読込中画面を出力します。

`isLoading`が`false`に更新されると、表示したい画面（ここではデータの一覧）を出力するようにしています。

### stateの初期値はtrue
コンポーネント読み込み時は当然データ読み込みが終わっていないので、`isLoading`は`true`とします。
そして、`finally`で`isLoading`を`false`、つまり読み込み完了とします。
しかし、これだとエラー時にも読み込み完了となってしまいます。
そのため、場合によってはエラー用の`state`も用意して、エラー画面を表示することもできます。

## Suspenseをつかったローディング画面
Reactの18以降を使っているのであれば、`Suspense`コンポーネントが使えます。
これは、子要素の読み込みが完了するまで、フォールバックを表示させることができるコンポーネントです。
※フォールバック：機能や性能を制限して動かすこと

どういうことか、コードを見てみます。
```react:SuspenseLoading
import { Suspense } from "react";

// API用URL
const url =
  "https://api.open-meteo.com/v1/forecast?latitude=35.6895&longitude=139.6917&hourly=temperature_2m&timezone=Asia%2FTokyo";

// APIレスポンスの型定義
interface WeatherData {
  hourly: {
    temperature_2m: number[];
    time: string[];
  };
}

let weatherData: WeatherData;

// APIデータ取得用処理
const fetchWeatherData = async (): Promise<WeatherData> => {
  const res = await fetch(url);
  const data = await res.json();
  weatherData = data;
  return data;
};

// コンポーネント描画処理
const WeatherDataComponent = () => {
  if (!weatherData) {
    // Promiseをthrow
    throw fetchWeatherData();
  }

  return (
    <>
      <h2>Temperature Forecast</h2>
      {weatherData.hourly && (
        <ul>
          {weatherData.hourly.time.map((time, index) => (
            <li key={time}>
              {time}: {weatherData.hourly.temperature_2m[index]}°C
            </li>
          ))}
        </ul>
      )}
    </>
  );
};

const SuspenseLoading = () => {
  return (
    <Suspense fallback={<h1>Loading...</h1>}>
      <WeatherDataComponent />
    </Suspense>
  );
};

export default SuspenseLoading;
```

少しコードが複雑になりましたが、ここでの動きは大きく2つです。
### コード概要
着目すべきは`SuspenseLoading`コンポーネントです。
ここでは`Suspence`コンポーネントを使用しています。

`fallback`には読み込みが完了するまでの間表示させたいコンポーネントを指定します。
コンポーネントの子要素には、表示させたいコンポーネントを指定します。

`Suspense`の使い方は大まかにこのような形となります。

しかし、子要素のコンポーネントに制約があるため、他の処理が若干複雑になっています。

### Promiseをスローする

https://ja.react.dev/reference/react/Suspense#displaying-a-fallback-while-content-is-loading

`Suspence`の制約は上記の公式サイトに以下のように書かれています。
> サスペンスコンポーネントをアクティブ化できるのはサスペンス対応のデータソースだけです。これには以下が含まれます：
>
> Relay や Next.js のようなサスペンス対応のフレームワークでのデータフェッチ
> lazy を用いたコンポーネントコードの遅延ロード
> use を用いたプロミス (Promise) からの値の読み取り

詳細な解説は避けますが、最後の部分に着目します。
子要素の中では`Promise`をスローします。

これにより、`Suspense`コンポーネントはスローされた`Promise`が解決されるまでは子要素の描画を保留します。
その代わり、`fallback`に指定したコンポーネントを描画することができます。

## まとめ
ローディング画面を標示する方法について調べました。
業務では`state`を使った方法で実装していました。

`Suspense`についてはまったく知らなかったので、今回の記事を通して少し理解を深めることができました。

しかし、そもそも`Promise`関連の知識も不足しているので、周辺知識も合わせて抑えておきたいと思いました。


## 参考にしたサイト
* [APIキーもログインも不要！完全無料で使える天気予報API「Open-Meteo」を使ってみた！](https://paiza.hatenablog.com/entry/2021/11/04/130000)
* [フォールバックとは｜「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word14810.html)
* [Reactでデータフェッチをする際にSuspenseを使おう!!](https://zenn.dev/y_ta/articles/4f93b83451c6f7)