TypeScriptでAPIリクエスト時のジェネリクス活用方法を徹底解説!AxiosとFetchでの型定義完全ガイド
生徒
「TypeScriptを使って、インターネット上のデータを取得するプログラムを作りたいです。でも、データの中身に型をつけるのが難しいと聞きました。」
先生
「データを通信でやり取りするときは、ジェネリクスという仕組みを使うと、安全で分かりやすくデータを扱うことができますよ。」
生徒
「ジェネリクスですか。少し難しそうですが、私のような初心者でも使えるようになりますか。」
先生
「仕組みを一つずつ順番に紐解いていけば、絶対に大丈夫です。それでは、基本的な使い方を見ていきましょう!」
1. API通信と型定義の基本とは?
私たちが普段、スマートフォンやパソコンでウェブサイトを見たりアプリを使ったりするとき、裏側では別のコンピュータとデータのやり取りをしています。このデータのやり取りをすることをAPI通信と呼びます。APIとは、アプリケーション・プログラミング・インターフェースの略称で、プログラム同士が自己紹介をしてデータを交換するための窓口のようなものです。
API通信でやり取りされるデータの多くは、JSONという形式のテキストデータです。JSONは、名前と値がペアになった、人間にもコンピュータにも分かりやすい書き方をしています。しかし、通常のJavaScriptでは、インターネットからダウンロードしてきたデータがどのような形をしているのか、プログラムを実行してみるまで分かりません。ここに大きな落とし穴があります。もし、存在すると思っていたデータが入っていなかったり、数字だと思っていた場所に文字が入っていたりすると、プログラムは途中で動かなくなってしまいます。
そこで活躍するのがTypeScriptです。TypeScriptでは、あらかじめ「このAPIからは、このような形のデータが返ってきます」という約束事を決めることができます。この約束事のことを型定義と呼びます。型定義をしっかりと行うことで、間違ったデータを扱おうとしたときに、プログラムを動かす前の段階でパソコンがエラーを教えてくれるようになります。未経験の方にとって、エラーが事前にわかることは、プログラムを安全に開発するための強力な味方になります。
2. ジェネリクスとは何か?分かりやすい例えで解説
TypeScriptの非常に便利な機能の一つに、ジェネリクスというものがあります。日本語では「汎用型」などと訳されますが、これだけでは意味が分かりませんね。簡単に例えるなら、ジェネリクスとは中身を後から自由に入れ替えられる透明な収納ボックスのようなものです。
例えば、書類を入れるための専用の箱を作るとします。ただの箱だと、中に何が入っているか外からは分かりません。そこで、「この箱には書類を入れます」というラベルを貼ることで、安全に書類を管理できるようになります。ジェネリクスは、このラベルを後から自由に変更できる仕組みです。あるときは「おもちゃの箱」、またあるときは「お菓子の箱」というように、箱の形そのものは同じままで、中に入れるものの種類を、使うときに指定できるのです。
プログラムの世界では、API通信の処理を行う関数という部品は、いつも同じ動きをします。指定されたインターネットの住所にアクセスし、データをダウンロードしてくるという動きです。しかし、ダウンロードしてくるデータの中身は、ユーザーの情報だったり、お天気の情報だったり、ブログの記事だったりと、毎回異なります。ここでジェネリクスを使うと、「データをダウンロードする機能」という一つの箱に対して、使うときに「今回はユーザーのデータだよ」「今回はお天気のデータだよ」と中身の型を教えてあげることができるようになります。これにより、同じ通信プログラムを何度も書き直す必要がなくなり、すっきりと綺麗なプログラムが書けるようになります。
3. Fetch機能を使った基本的なAPI通信のやり方
まずは、ブラウザに標準で備わっているFetchという機能を使って、インターネットからデータを取得する一番シンプルな方法を見ていきましょう。Fetchは、特別な準備をしなくても、JavaScriptやTypeScriptで最初から使える便利な機能です。指定したインターネットの住所にリクエストを送り、お返事を受け取ることができます。
今回は、ユーザーの「名前」と「年齢」を持つデータを取得する場面を想定してみましょう。プログラムの中で、どのようなデータが返ってくるのかを明示するために、interfaceというものを使ってデータの型を宣言します。それでは、実際のプログラムコードを見てみましょう。
interface User {
name: string;
age: number;
}
function getUserData() {
fetch("https://example.com/api/user")
.then(response => response.json())
.then((data: User) => {
console.log("ユーザーの名前は", data.name);
console.log("ユーザーの年齢は", data.age);
});
}
上記のコードでは、Userという型を作成し、名前が文字列、年齢が数字であることを定義しています。そして、Fetchによってデータを受け取った段階で、そのデータがUserの形をしていることを指定しています。このように書くことで、開発中の画面でdata.と入力したときに、自動的にnameやageが候補として表示されるようになり、打ち間違いを防ぐことができます。
4. Fetchでジェネリクスを活用する具体的なプログラム
先ほどの例では、一つの特定のデータしか扱えませんでした。今度は、ジェネリクスを活用して、どんなデータでも受け取ることができる汎用的な通信関数を作ってみましょう。型を指定する部分に、アルファベットの大文字のTという記号を使います。このTは「Type」の頭文字で、後から好きな型に置き換えることができる、仮の型という意味を持っています。
関数名の後ろに山括弧をつけ、その中にTを書くことで、この関数はジェネリクスを使用しますよ、という宣言になります。実際の書き方を確認してみましょう。
async function downloadData<T>(url: string): Promise<T> {
const response = await fetch(url);
const data = await response.json();
return data as T;
}
interface Item {
id: number;
title: string;
}
async function start() {
const item = await downloadData<Item>("https://example.com/api/item");
console.log("商品の番号は", item.id);
console.log("商品の名前は", item.title);
}
このプログラムでは、downloadDataという関数がジェネリクスに対応しています。関数を呼び出すときに、downloadData<Item>と書くことで、仮の記号だったTが、すべてItemという具体的な型に置き換わります。これにより、この関数は「商品をダウンロードして、商品の型として返す関数」に大変身します。もし別のデータを取得したければ、山括弧の中身を別の型に変えるだけで、同じ関数を使い回すことができます。
5. Axiosライブラリとは何か?Fetchとの違いについて
API通信を行う際、Fetchの他にもう一つ非常によく使われるのがAxiosというライブラリです。ライブラリとは、便利なプログラムがぎゅっと詰まった道具箱のようなものです。Fetchは標準機能なのでそのまま使えますが、実際のアプリ開発現場では、より機能が豊富で使いやすいAxiosが選ばれることが多くあります。
Axiosを使う最大のメリットの一つは、最初からジェネリクスを使うための仕組みが綺麗に組み込まれている点です。Fetchの場合、データをダウンロードした後に、自分で無理やり型を当てはめる作業が必要でしたが、Axiosではデータをリクエストする瞬間に型を指定することができます。また、インターネット通信が失敗したときのエラー処理が最初から分かりやすくなっていたり、古いブラウザでも安定して動いたりと、初心者にとっても扱いやすい工夫がたくさん施されています。パソコンの操作に慣れていない段階でも、Axiosを使うことで、より短い行数で安全なコードを書くことができるようになります。
6. Axiosでジェネリクスを使って安全にデータを取得する方法
それでは、Axiosを使ってAPIからデータを取得するプログラムを書いてみましょう。Axiosでは、axios.getという命令を使ってデータを取得します。この命令の後ろに、山括弧を使って型を指定するだけで、自動的にダウンロードされたデータにその型が適用されます。
今回は、ブログの記事データを取得する場面を考えてみます。記事には、管理用の番号、記事のタイトル、そして本文が含まれているとします。これを表現する型を用意して、Axiosと組み合わせてみましょう。
import axios from "axios";
interface Article {
id: number;
title: string;
content: string;
}
async function getArticle() {
const response = await axios.get<Article>("https://example.com/api/article");
const article = response.data;
console.log("記事のタイトルは", article.title);
console.log("本文の長さは", article.content.length);
}
プログラムの中で、axios.get<Article>と書いている部分がポイントです。これだけで、受け取った変数responseの中にあるdataという項目が、自動的にArticleの型になります。そのため、パソコンは「article.titleは文字列だな」と完全に理解することができ、文字の長さを数えるlengthなどの命令を安心して使うことができるようになります。
7. 通信エラーが発生したときの対策とエラーハンドリング
インターネットの通信は、いつも必ず成功するとは限りません。パソコンがオフラインになっていたり、データを管理しているサーバーが故障していたりすると、通信は失敗してしまいます。プログラムが途中で完全に止まってしまわないように、失敗したときの対策をしておく必要があります。この対策のことをエラーハンドリングと呼びます。
TypeScriptで通信エラーを処理するときは、tryとcatchという命令を使います。「これを試してみて、もしダメだったらこっちの処理をしてね」という仕組みです。実際のプログラムを見てみましょう。
import axios from "axios";
interface Profile {
nickname: string;
}
async function loadProfile() {
try {
const response = await axios.get<Profile>("https://example.com/api/profile");
console.log("ニックネーム", response.data.nickname);
} catch (error) {
console.log("通信に失敗しました。電波の良い場所で再度お試しください。");
}
}
このように、tryのブロックの中に本来行いたい通信の処理を書き、catchのブロックの中に失敗したときの処理を書きます。これにより、万が一インターネットが繋がっていなくても、画面に変なエラー文字が出る代わりに、分かりやすいメッセージを表示してプログラムを安全に動かし続けることができます。ジェネリクスで成功時の型を保証しつつ、エラーの対策も行うことが、一人前のプログラムを作る大切なステップです。
8. 未経験者が気をつけるべき型定義の注意点とアドバイス
最後に、プログラミングを始めたばかりの方が陥りやすい注意点についてお話しします。API通信でジェネリクスを使うときは、「自分がプログラムで書いた型定義」と「実際にインターネットから降ってくるデータ」が、本当に一致しているかどうかに注意する必要があります。
ジェネリクスは非常に強力ですが、これはあくまで「パソコンの画面上で型を信じ込ませる仕組み」です。もし、プログラム側で「年齢は数字です」と書いていても、実際のAPIが「年齢は二十歳です」という文字列のデータを送ってきた場合、TypeScriptはそれを事前に見抜くことができません。そのため、初めて扱うAPIのデータを型定義するときは、まずは一度簡単なプログラムでデータの中身を画面に表示して確認するか、仕様書と呼ばれるAPIの説明書をよく読んでから型を作るようにしてください。
また、どうしても型が分からないときは、何でも許容するanyという特別な型を使いたくなるかもしれませんが、これを多用するとTypeScriptを使う意味がなくなってしまいます。分からない項目があるときは、最初は最低限必要な項目だけを型定義することから始めてみてください。少しずつ正確な型を組み立てていくことが、スキルアップへの一番の近道です。