TypeScriptでAPI呼び出しをラップするユーティリティ関数の設計完全ガイド!初心者向けにAxiosやFetchの使い方を解説
生徒
「ホームページを作っているのですが、別のシステムからデータを引っ張ってくるAPI通信というものが難しくて困っています。TypeScriptでおすすめの方法はありますか?」
先生
「TypeScriptでは、FetchやAxiosという仕組みを使ってAPI通信を行います。それを毎回書くと大変なので、ラップするユーティリティ関数という便利な共通の部品を作って設計すると、驚くほど楽になりますよ。」
生徒
「ラップとか、ユーティリティ関数とか、難しい言葉がいっぱいです。パソコン初心者でも分かりますか?」
先生
「大丈夫です。インターネットの仕組みから、共通の部品をどのように作るかまで、基本的な使い方を順番に見ていきましょう!」
1. API通信とFetchやAxiosの基本を知ろう
まずは、パソコンを触ったことがない方でもイメージできるように、基本的な言葉の意味から解説します。API通信とは、あなたのパソコンの画面と、インターネットの向こう側にある大きなコンピュータ(サーバーといいます)の間でお手紙のやり取りをすることです。例えば、天気予報のアプリを開いたときに最新の天気が表示されるのは、このAPI通信を使って最新のデータを引っ張ってきているからです。
このお手紙のやり取りをプログラムで実現するための道具が、FetchやAxiosです。Fetchは、最初から標準の機能として備わっている道具です。一方でAxiosは、後から追加して使う、より高機能で親切な道具です。どちらを使ってもAPI通信は可能ですが、これらの道具をそのまま何度も使うと、プログラムのあちこちが同じような仕組みだらけになってしまい、間違いを見つけるのが大変になります。
2. ラップするとユーティリティ関数の重要性
ここで登場するのが、ラップとユーティリティ関数という考え方です。ラップとは、英語でお肉やお野菜を包む「サランラップ」と同じ意味です。難しいプログラムや複雑な手順を、使いやすいように優しく包み込んで隠してしまうことを言います。そして、その包み込んだ便利な道具のことをユーティリティ関数、あるいは共通部品と呼びます。
例えば、毎回おなじ宛先にお手紙を書くときに、郵便番号や住所、自分の名前を何度も書くのは面倒ですし、書き間違える原因になります。そこで、あらかじめ「ボタンを一つ押すだけで自動的に宛先が書かれた封筒が用意される魔法の引き出し」を作っておきます。これがユーティリティ関数の役割です。TypeScriptという言語を使ってこの共通部品をしっかりと設計しておくと、安全で綺麗なプログラムを作ることができます。
3. データを取得する基本的な関数を作ってみよう
それでは、具体的にFetchという道具を包み込んで、簡単な共通の部品を作ってみましょう。今回は、インターネット上からユーザーの情報を取得する関数を想定します。初心者の方にも分かりやすいように、まずは一番シンプルな形から記述します。
async function simpleGetRequest(url: string) {
const response = await fetch(url);
const data = await response.json();
return data;
}
ここで使われているいくつかの難しい言葉を解説します。asyncとawaitは、インターネットの通信待ちをするときのおまじないです。通信には少し時間がかかるので、「通信が終わるまでここで待っていてね」という指示をプログラムに出しています。urlは、通信相手のインターネット上の住所のことで、文字のデータ(string型)として受け取ります。最後にjsonという言葉がありますが、これはデータを受け取るときに、パソコンが読みやすい形式に変換する処理のことです。
4. 通信エラーに備える安全な設計方法
インターネットの通信は、常に成功するとは限りません。パソコンが電波の届かない場所に移動してしまったり、相手のサーバーが壊れていたりすることもあります。そんなときにプログラムが突然動かなくなって画面が真っ白になってしまうと、使っている人が困ってしまいます。そのため、通信が失敗したときの処理(例外処理やエラーハンドリング)をあらかじめ仕込んでおく必要があります。
プログラムの世界では、失敗しそうな場所をtryとcatchという専用の枠組みで囲うルールがあります。「まずはこれを試してみて(try)、もし失敗したらここで捕まえて対処してね(catch)」という意味になります。この仕組みを取り入れた、より安全なユーティリティ関数の書き方を確認しましょう。
async function safeGetRequest(url: string) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("通信が失敗しました");
}
const data = await response.json();
return data;
} catch (error) {
console.log("エラーが発生しました。内容を確認してください。");
return null;
}
}
このプログラムでは、もし通信相手の返事がおかしいとき(!response.okのとき)に、自分から「失敗したよ」というエラーを発生させています。すると、すぐに下のcatchの部分へ処理がジャンプします。そこで画面にエラーの内容を記録し、中身が空っぽであることを意味するnullという特別な値を返します。これにより、予期せぬトラブルが起きても全体の動きが止まらないようになります。
5. Axiosを使ったより高度な共通部品の設計
次に、実際の開発現場でよく使われるAxiosという道具を使った共通部品の設計に挑戦してみましょう。Axiosを使うと、Fetchのときよりも短いコードで、より多くの便利な機能を使うことができます。例えば、通信の有効期限(タイムアウト)を設定したり、毎回必ず送信する共通の合言葉(認証トークン)を自動的にセットしたりすることが可能です。
ここでは、特定のサーバーに対してデータを送信する(POST送信といいます)ためのユーティリティ関数を作成します。新しい会員登録をするときなどに、名前やパスワードを相手に送り届けるイメージです。
import axios from "axios";
async function postUserData(url: string, payload: any) {
try {
const response = await axios.post(url, payload);
return response.data;
} catch (error) {
console.log("データを送ることができませんでした");
return undefined;
}
}
プログラムの中にあるpayloadという単語は、相手に届けたい「荷物(データ)」そのものを指しています。Axiosの素晴らしいところは、Fetchのときに行っていたデータを変換する処理を自動で行ってくれる点です。axios.postと書くだけで、指定した住所へ荷物を安全かつ確実に送り届けてくれます。失敗したときは、何もないことを意味するundefinedを返すように設計しています。
6. 型の安全性を取り入れてTypeScriptの魅力を引き出そう
ここまでは基本的な動きを解説してきましたが、ここでTypeScriptの最大の武器である型(Type)の機能を共通部品に組み込んでみましょう。型とは、プログラムが扱うデータの「種類や設計図」のことです。例えば、「このデータには必ず名前(文字列)と年齢(数字)が入っています」という約束事をあらかじめ決めておくことができます。
型をしっかりと決めておくと、間違えて数字を入れるべき場所に文字を入れてしまったときに、パソコンが実行する前に「そこは間違っているよ」と赤線を出して教えてくれます。これがバグを未然に防ぐ強力な仕組みになります。ユーティリティ関数を作る際、どんなデータが返ってくるかを自由に指定できるように、ジェネリクス(Generics)という便利な技術を使います。ジェネリクスは、型をあとから自由に入れ替えられる箱のようなものです。
interface User {
id: number;
name: string;
}
async function typedFetchRequest<T>(url: string): Promise<T | null> {
try {
const response = await fetch(url);
if (!response.ok) {
return null;
}
const data: T = await response.json();
return data;
} catch (error) {
return null;
}
}
コードの中にある<T>という記号がジェネリクスです。関数を呼び出す側が、「今回はUserの形のデータが欲しいです」と指定すると、このTの部分がすべてUserに置き換わります。Promise<T | null>というのは、「将来的に指定された型、もしくは空っぽのデータが返ってくることを約束します」という宣言です。これにより、開発者は返ってきたデータを安心して使うことができるようになります。
7. 作成した共通部品を実際に画面で動かす手順
共通部品が完成したら、それを実際にどのように呼び出して使うのか、具体的な手順を見ていきましょう。先ほど定義した型と、型安全なユーティリティ関数を組み合わせて、インターネットからデータを引っ張ってきて画面に表示するまでの流れを確認します。
async function displayUserInfo() {
const targetUrl = "https://example.com/api/user/1";
const result = await typedFetchRequest<User>(targetUrl);
if (result !== null) {
console.log("お名前は " + result.name + " です。");
} else {
console.log("データの取得に失敗したため、情報を表示できません。");
}
}
この処理の実行結果は、通信が成功したか失敗したかによって、それぞれ以下のように出力されます。成功した場合は、相手のコンピュータから受け取った名前が正しく組み込まれて表示されます。
お名前は 山田太郎 です。
もしインターネットの接続が悪く、通信に失敗した場合は、ユーティリティ関数が自動的に安全なエラー処理を行い、画面が壊れる代わりに次のメッセージを出力します。
データの取得に失敗したため、情報を表示できません。
このように、一つの便利なユーティリティ関数を作っておけば、これからは何十箇所、何百箇所とある通信処理を、すべてこの短い記述だけで安全に行うことができるようになります。これが、TypeScriptでAPI呼び出しをラップする共通部品の設計が持つ本当の価値です。一見すると難しそうに見えますが、基本を一つずつ組み立てていけば、初心者の方でも必ず綺麗なプログラムが書けるようになります。