TypeScriptでAPI通信のローディングとエラー状態管理を徹底解説!AxiosとFetchの使い方初心者ガイド
生徒
「インターネットからデータを読み込むときに、画面が止まったようになって、失敗したのか動いているのか分からない状態になってしまいます。TypeScriptでうまく状態を管理する方法はありますか?」
先生
「インターネットを通じた通信では、データを読み込んでいる最中のローディング状態や、通信が失敗したときのエラー状態を正しく画面に伝えることがとても大切です。TypeScriptを使えば、今どのような状態なのかを安全に、そして分かりやすく管理することができますよ。」
生徒
「ローディング中やエラーの処理をどのようにプログラムで書けばいいのか、詳しく知りたいです!」
先生
「パソコンの操作に慣れていない方でも理解できるように、仕組みから具体的な書き方まで順番に学んでいきましょう!」
1. API通信と状態管理の基本とは?
私たちが普段使っているスマートフォンやパソコンのアプリは、インターネットの向こう側にある大きなコンピューターから情報を取得して画面に表示しています。このインターネットを通じた情報のやり取りをAPI通信と呼びます。API通信は、ボタンを押した瞬間に一瞬で終わるわけではありません。データを送ってもらって手元に届くまでには、必ずわずかな時間がかかります。
このデータが届くまでの待ち時間のことをローディング中と呼びます。また、インターネットの接続が悪かったり、相手のコンピューターが故障していたりして、データを正しく受け取れなかったときのことをエラーと呼びます。今、通信がどのような状況にあるのかをプログラムの中で明確に区別して記録しておく作業を状態管理と言います。
状態管理を適切に行わないと、画面には何も表示されず、動いているのか壊れているのかが利用者に伝わりません。利用者が安心してアプリを使えるようにするために、ローディングとエラーの状態管理は必須の知識となります。
2. なぜTypeScriptでローディングとエラーを管理するのか
TypeScriptを使う最大の理由は、プログラムの間違いを未然に防いでくれる仕組みがあるからです。通常のプログラミング言語では、ローディング中という状態の名前を間違えて入力してしまっても、実行するまで間違いに気づけないことがあります。しかし、TypeScriptでは、あらかじめ状態の種類を厳しく定義しておくことができます。
例えば、「準備中」「通信中」「成功」「失敗」という四つの状態以外は絶対に受け付けない、という規則を作ることができます。これにより、開発者が文字を入力し間違えたり、存在しない状態を勝手に作り出したりすることを防げます。型というルールを使って状態を縛ることで、誰が読んでも理解しやすく、不具合の起きにくい安全なプログラムを組み立てることが可能になります。
3. 状態を表す文字や数値を文字型で定義する
まずは、現在の通信状態がどのような形をしているかを定義することから始めましょう。TypeScriptでは、状態の種類を文字の組み合わせで表現することが得意です。最も単純な方法として、現在の状態を表す専用の型を作成します。
ここでは、通信の状態を「まだ何もしていない状態」「通信している最中」「通信が成功した状態」「通信が失敗した状態」の四つに分けて、それぞれの状態を文字として定義してみます。このように、あらかじめ決められた文字だけを許可する仕組みを、TypeScriptでは列挙型やリテラル型と呼びます。まずは一番基本となる、状態を管理するための変数の作り方を見てみましょう。
// 通信の状態を表す専用の型を作ります
type ApiState = "idle" | "loading" | "success" | "error";
// 現在の状態を記録する変数を準備します。最初は「まだ何もしていない状態」です
let currentStatus: ApiState = "idle";
// 通信が始まったら、状態を「通信中」に変更します
currentStatus = "loading";
console.log("現在の通信状態は次の通りです:", currentStatus);
上記のプログラムでは、変数に許可されていない文字を入れようとすると、パソコンがすぐにエラーを教えてくれます。このようにして、状態のすれ違いが起きないように土台を作ります。
4. Fetchを使った基本的な通信と状態の変化
次に、標準で備わっている通信の仕組みであるFetchを使って、実際にデータを取得しながら状態を切り替える処理を書いてみましょう。Fetchは、指定したインターネットの住所にデータをちょうだいとお願いを出す機能です。
通信を行うときは、まず状態を「通信中」に変更します。そして、データの取得がうまくいったら状態を「成功」に変え、もし途中で通信が途切れるなどの問題が発生したら状態を「失敗」に変更します。このように、通信の進行状況に合わせて、変数の内容をパズルのように書き換えていきます。
type StatusType = "idle" | "loading" | "success" | "error";
let currentLoadingStatus: StatusType = "idle";
let errorMessage: string = "";
function startFetchData() {
// 1. 通信を開始するので、状態をローディング中にします
currentLoadingStatus = "loading";
console.log("状態が変更されました:", currentLoadingStatus);
// インターネットの住所にお願いを出します(ここでは例えの住所です)
fetch("https://example.com/api/data")
.then((response) => {
// 2. 成功したら状態を成功にします
currentLoadingStatus = "success";
console.log("状態が変更されました:", currentLoadingStatus);
})
.catch((error) => {
// 3. 失敗したら状態をエラーにして、原因を記録します
currentLoadingStatus = "error";
errorMessage = "データの読み込みに失敗しました。";
console.log("状態が変更されました:", currentLoadingStatus);
});
}
startFetchData();
実行したときの画面の動きをシミュレーションすると、以下のような順番で文字が出力されます。
状態が変更されました: loading
状態が変更されました: success
通信が成功した場合は上記のように流れますが、もしインターネットがつながっていない場合は、最後の部分がエラーに切り替わります。
5. Axiosを使ったより高度なエラー判定の方法
続いて、実務でよく使われるAxiosという道具を使った方法について解説します。Axiosは、先ほどのFetchをさらに使いやすく便利にした外付けの部品です。Axiosを使うと、エラーが発生したときに、それがどのような原因で起きたのかをより細かく調べることができます。
例えば、相手のコンピューターが見つからなかったのか、それともパスワードの入力が間違っていたのかといった原因を区別できます。TypeScriptとAxiosを組み合わせることで、エラーの内容を安全に読み解き、画面に親切な案内文を表示することが可能になります。
import axios from "axios";
type ConnectionState = "idle" | "loading" | "success" | "error";
let globalState: ConnectionState = "idle";
let errorDetail: string = "";
async function downloadInformation() {
// 通信を開始します
globalState = "loading";
try {
// Axiosを使ってデータをダウンロードします
const response = await axios.get("https://example.com/api/user");
// 無事に届いたら成功状態にします
globalState = "success";
} catch (error) {
// トラブルが発生した場合はここに入ります
globalState = "error";
// Axiosのエラーであるかを確認する安全な仕組みです
if (axios.isAxiosError(error)) {
errorDetail = "通信エラーが発生しました: " + error.message;
} else {
errorDetail = "予想外のトラブルが発生しました。";
}
console.log(errorDetail);
}
}
downloadInformation();
このプログラムでは、エラーが起きた原因が文字として変数に保存されるため、画面を見た人に対して「何が原因で失敗したのか」を言葉で伝えることができるようになります。
6. オブジェクトを使って状態を一括で綺麗にまとめる
これまでは、ローディングの状態やエラーの文字を、それぞれバラバラの変数で管理していました。しかし、変数が増えてくると、どの変数がどの通信に対応しているのかが分からなくなってしまいます。そこで、一つの箱の中にすべての情報をまとめて入れる方法を使います。この箱のことをオブジェクトと呼びます。
オブジェクトを使うと、「今ローディング中であるか」「エラーメッセージはあるか」「届いたデータは何か」を一つのまとまりとして管理できるため、プログラムの全体像が非常にすっきりします。初心者の方にとっても、関連するデータが一箇所に集まっているため、読みやすさが格段に向上します。
// 通信に関するすべての情報を一つの型として定義します
interface ApiCommunicationPack {
isLoading: boolean;
errorMessage: string | null;
receivedData: string[] | null;
}
// 最初の空っぽの状態を作ります
let myCommunication: ApiCommunicationPack = {
isLoading: false,
errorMessage: null,
receivedData: null
};
// 通信が始まったときの処理を再現します
function processLoadingStart() {
myCommunication.isLoading = true;
myCommunication.errorMessage = null;
myCommunication.receivedData = null;
}
processLoadingStart();
console.log("現在のまとまった状態:", myCommunication);
このようにデータをひとまとめにしておくことで、複数の通信が同時に発生するような複雑な画面でも、中身がごちゃ混ぜにならずに綺麗に整理整頓ができます。
7. 実際の画面を想定した条件分岐のシミュレーション
状態の管理ができるようになったら、最後はそれぞれの状態に応じて、画面にどのような案内を表示するかを決定する条件分岐の処理を作ります。条件分岐には、以前に学んだif文の仕組みをそのまま応用することができます。
「もしローディング中なら、ぐるぐる回るマークを表示する」「もしエラーなら、警告の文章を表示する」「成功したら、届いた中身を表示する」というように、状態の値によって案内を切り替えます。以下のプログラムは、現在の状態を読み取って、表示する内容をパッと切り替えるデモンストレーションです。
interface DisplayState {
status: "loading" | "error" | "success";
message: string;
}
function createDisplayView(state: DisplayState) {
if (state.status === "loading") {
return "画面を開いています。少々お待ちください...";
}
if (state.status === "error") {
return "エラーが発生しました。理由: " + state.message;
}
if (state.status === "success") {
return "データの読み込みが完了しました!表示します: " + state.message;
}
return "未知の状態です。";
}
// 通信中を想定したテストです
const loadingTest = createDisplayView({ status: "loading", message: "" });
console.log(loadingTest);
// エラーを想定したテストです
const errorTest = createDisplayView({ status: "error", message: "接続がタイムアウトしました" });
console.log(errorTest);
このプログラムを実行すると、それぞれの状態に合わせた親切な文章が正しく作られていることが確認できます。
画面を開いています。少々お待ちください...
エラーが発生しました。理由: 接続がタイムアウトしました
この仕組みを実際のホームページの画面と連動させることで、ボタンを押したときに「通信中」と表示され、終わったら「完了しました」と切り替わる、使いやすいシステムが完成します。TypeScriptの型に守られた状態管理を身につけて、不具合のないスマートな通信処理を書いていきましょう。