TypeScriptでジェネリック型とインターフェースを組み合わせる方法をやさしく解説!
生徒
「先生、TypeScriptでジェネリック型とインターフェースを一緒に使うことってできるんですか?」
先生
「もちろんできますよ。ジェネリック型は、どんな型にも対応できる“型のテンプレート”のような仕組みなんです。」
生徒
「テンプレートって、どういう意味ですか?」
先生
「たとえば、ひとつのインターフェースを使って“数値型”にも“文字列型”にも対応できるようにしたいときに使います。とても便利ですよ!」
生徒
「なるほど!それなら、具体的な書き方を知りたいです!」
先生
「それでは、ジェネリック型とインターフェースの組み合わせ方を具体的に見ていきましょう。」
1. ジェネリック型とは?
まず、ジェネリック型とは、どんな型でも受け取れるようにするための仕組みです。TypeScriptの型は通常、stringやnumberなどを明示的に指定しますが、ジェネリック型を使うと柔軟に対応できます。たとえば、「いろんな型のデータを扱うクラスや関数を作りたい」ときに便利です。
ジェネリック型は、アルファベットの<T>のような記号を使って表します。このTは「型の変数」のようなもので、好きな型が入る場所を意味します。
2. インターフェースとジェネリック型を組み合わせる基本
インターフェースは、オブジェクトの構造(どんなプロパティがあり、どんな型なのか)を定義する仕組みです。ジェネリック型と組み合わせることで、「プロパティの型をあとから自由に指定できる」ようになります。
まずは、基本的な例を見てみましょう。
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// number型を使う例
const numberResponse: ApiResponse<number> = {
data: 123,
status: 200,
message: "成功しました"
};
// string型を使う例
const stringResponse: ApiResponse<string> = {
data: "OK",
status: 200,
message: "処理完了"
};
このように、<T>をインターフェースに追加することで、Tの部分を好きな型に差し替えられます。たとえばApiResponse<number>ならdataが数値型、ApiResponse<string>なら文字列型として扱えます。
3. 型を後から指定できるメリット
この方法の最大のメリットは、再利用性が高いことです。もしジェネリックを使わなければ、「数値用のApiResponse」「文字列用のApiResponse」と複数のインターフェースを作る必要がありました。
しかし、ジェネリックを使えばひとつの定義で済むため、保守も簡単です。開発の現場では、API通信やフォーム入力などで、このようなパターンが頻繁に登場します。
4. 複数のジェネリック型を使う例
ジェネリック型は1つだけでなく、複数指定することもできます。たとえば、<T, U>のように2つの型を扱いたい場合です。
interface Pair<T, U> {
first: T;
second: U;
}
const pair1: Pair<string, number> = {
first: "年齢",
second: 25
};
const pair2: Pair<boolean, string> = {
first: true,
second: "有効"
};
このように、ジェネリックを複数指定すると、異なる型を自由に組み合わせられます。フォームデータや設定値を扱うときに、とても便利です。
5. ジェネリック型とインターフェースを使った関数の引数例
インターフェースを使ったオブジェクトを、関数の引数として使うこともよくあります。次の例では、APIレスポンスを処理する関数を作っています。
function printResponse<T>(response: ApiResponse<T>): void {
console.log("ステータス:", response.status);
console.log("メッセージ:", response.message);
console.log("データ:", response.data);
}
const userResponse: ApiResponse<{ name: string; age: number }> = {
data: { name: "田中", age: 28 },
status: 200,
message: "ユーザー情報を取得しました"
};
printResponse(userResponse);
ステータス: 200
メッセージ: ユーザー情報を取得しました
データ: { name: '田中', age: 28 }
このように、ApiResponse<T>を使うことで、Tの部分をどんな型にも柔軟に対応させることができます。オブジェクトの中にオブジェクトが入っていても問題ありません。
6. 制約(extends)を使って型を限定する
ジェネリック型はとても柔軟ですが、自由すぎると予期せぬ型エラーが起こることもあります。そこで、extendsを使って「この型は○○を持っている必要がある」と制約を加えることができます。
interface HasId {
id: number;
}
interface DataContainer<T extends HasId> {
item: T;
}
const userData: DataContainer<{ id: number; name: string }> = {
item: { id: 1, name: "佐藤" }
};
T extends HasIdという書き方をすることで、「Tは必ずidプロパティを持っていなければならない」というルールを設定できます。これにより、誤ったデータ構造を事前に防ぐことができます。
7. 実践的な使い方のヒント
実際の開発では、ジェネリック型とインターフェースを組み合わせて、API通信の型定義や、フォーム入力データの型設計によく使われます。これにより、データの整合性を保ちながら、再利用可能で堅牢なコードを書くことができます。
たとえば、ユーザー情報、商品データ、設定データなど、異なる種類のデータを共通の構造で扱いたい場合に非常に便利です。TypeScriptの型チェック機能と合わせて使うことで、バグの少ないアプリケーションを作ることができます。
まとめ
ここまで、TypeScriptでジェネリック型とインターフェースを組み合わせて使う方法を順を追って見てきました。記事を読み終えた今あらためて振り返ってみると、ジェネリック型が「型の枠組みを柔軟に変えられる仕組み」であり、インターフェースが「オブジェクトの形を決める設計書のような存在」であることがより自然に理解できてきます。特に、ジェネリック型とインターフェースを組み合わせることで、ひとつの定義を使い回しながら異なる型のデータを安全に扱えるという利点は、実際の開発に直結する大きな魅力です。 たとえば、API通信で受け取るデータ構造が毎回違っても、ジェネリック型を使えば共通のインターフェースに適応させられますし、フォーム入力や設定データなどの扱いでも応用が効きます。さらに、複数のジェネリック型を組み合わせたり、extendsによる制約を加えたりすることで、より堅牢で信頼性の高い型設計が可能になります。 特に初心者の段階では「ジェネリック型はむずかしそう」と感じやすいですが、実際には「あとから自由に型を決められる便利な枠」だと理解してしまえば、むしろコードの読みやすさや再利用性が向上し、プログラミング全体が扱いやすくなります。ジェネリック型はサンプルを見るよりも、実際に自分で少し触ってみると理解が一気に進むため、この記事の内容を踏まえて、簡単な型を自分で書き換えながら学ぶとより身につきやすいでしょう。
ジェネリック型とインターフェースを組み合わせたサンプル
ここでは、記事全体の振り返りとして、ジェネリック型とインターフェースを応用した少し発展的なサンプルをまとめます。複雑に見えますが、基本の組み合わせ方が理解できていれば自然と読み解けるはずです。
interface Result<T, U> {
info: T;
meta: U;
success: boolean;
}
function createResult<T, U>(info: T, meta: U): Result<T, U> {
return {
info,
meta,
success: true
};
}
const userInfo = createResult(
{ id: 10, name: "山田" },
{ createdAt: "2025-01-01", type: "User" }
);
const productInfo = createResult(
{ id: 5, price: 3000 },
{ createdAt: "2025-02-10", type: "Product" }
);
このサンプルでは、異なる種類のデータを共通の構造で扱いながらも、それぞれに合った型を保持しつつ安全に操作することができます。こうした工夫は、データの種類が多くなりやすい現場でとても重宝します。 また、ジェネリック型によって型の矛盾が起きにくく、開発中に発生しがちな思わぬエラーを事前に防ぐ効果もあります。TypeScriptを使うメリットがしっかり体感できる仕組みといえます。
生徒
「先生、今日の内容を振り返ってみると、ジェネリック型が意外と身近に使える仕組みだってわかりました!」
先生
「そうですね。特に、データ構造がバラバラなときでも統一した型で扱えるところが大きな利点です。インターフェースと合わせるとより力を発揮しますよ。」
生徒
「複数のジェネリック型を使ったり、extendsで制約を付けたりするところは応用的な感じでしたけど、実際の開発でよく出てきそうだと思いました。」
先生
「その通りです。特にAPIのレスポンス型はほとんどジェネリックで設計されるので、今回の内容を押さえておけば実務でも戸惑いにくいはずです。」
生徒
「ジェネリック型が“あとから型を変えられる箱”のような存在だと考えたら、すごく理解しやすくなりました。今後は意識して使ってみます!」
先生
「良い考え方ですよ。自由に使えるようになるとTypeScriptの設計力が大きく上がりますから、ぜひ積極的に触ってみてください。」