はじめに
業務で、データを取得している間は画面を見せないようにするローディング画面を作る事があったので、備忘のためにその方法をまとめていきます。
環境
以下のものを使っています。
ライブラリ | バージョン |
---|---|
React | 18.2.0 |
TypeScript | 5.2.2 |
Vite | 5.2.0 |
useStateをつかったローディング画面
やろうと思ったときにパッと思い浮かぶのはこれかなと思います。
そんなに難しいことはないので、早速コードを見ていきましょう。
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
コンポーネントが使えます。
これは、子要素の読み込みが完了するまで、フォールバックを表示させることができるコンポーネントです。
※フォールバック:機能や性能を制限して動かすこと
どういうことか、コードを見てみます。
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をスローする
Suspence
の制約は上記の公式サイトに以下のように書かれています。
サスペンスコンポーネントをアクティブ化できるのはサスペンス対応のデータソースだけです。これには以下が含まれます:
Relay や Next.js のようなサスペンス対応のフレームワークでのデータフェッチ
lazy を用いたコンポーネントコードの遅延ロード
use を用いたプロミス (Promise) からの値の読み取り
詳細な解説は避けますが、最後の部分に着目します。
子要素の中ではPromise
をスローします。
これにより、Suspense
コンポーネントはスローされたPromise
が解決されるまでは子要素の描画を保留します。
その代わり、fallback
に指定したコンポーネントを描画することができます。
まとめ
ローディング画面を標示する方法について調べました。
業務ではstate
を使った方法で実装していました。
Suspense
についてはまったく知らなかったので、今回の記事を通して少し理解を深めることができました。
しかし、そもそもPromise
関連の知識も不足しているので、周辺知識も合わせて抑えておきたいと思いました。