この記事の最終更新日: 2025年5月4日
Webアプリ開発を進める中で、同じような処理を何度も書き直した経験はありませんか?一方で、どこかで型チェックを甘くしてしまうと、思わぬところでエラーが起こりがちです。そこで登場するのがジェネリクス(Generics)です。ジェネリクスを使うと、これ1つでいろいろな型を扱える関数やクラスを作りながら、TypeScriptの型チェックもしっかり活かせるようになります。

ジェネリクスって何?
ジェネリクスは「型のひな形」のようなものです。最初に関数やクラスを作るとき、具体的な型を決めずに「あとで型を教えてね!」とだけ書いておき、実際に使うときに本当の型を指定します。
箱をイメージするとわかりやすい
箱に何を入れるか分からないうちは、箱の設計図だけ作っておきましょう。実際に荷物を入れるときに「これは本だよ」とか「これは数値だよ」と伝えるイメージです。コードで書くと
class Box<T> {
constructor(public content: T) {}
}
// 使うときに「これがTだよ」と教えます
const textBox = new Box<string>("Hello"); // 中身は文字列
const numberBox = new Box<number>(123); // 中身は数値
箱の中身が何かを変えたいたびにクラスを作り直す必要がなく、設計図は1つでOK。これがジェネリクスの便利さです。
関数で使うジェネリクス
関数にも同じ考え方が使えます。値をそのまま返す「identity関数」を見てみましょう。
function identity<T>(value: T): T {
return value;
}
// 呼び出し例
const a = identity<string>("TypeScript"); // aはstring型
const b = identity(42); // bはnumber型(自動で判別)
<T>
は「あとで決める型」を表しています。- 明示的に
<string>
と書いても、引数から型を想像(推論)してくれてもOKです。
いくつかの型を同時に扱うとき
複数の値をペアで返す関数もジェネリクスで書けます。
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p = pair("名前", 30); // pの型は [string, number]
T
、U
といったパラメータをカンマでつなげて、好きなだけ増やせます。(
型パラメータはいくつでも増やせるし、アルファベットなんでもOKだし、単語でも良い
T
、U
といったパラメータはカンマで区切って、必要なだけ増やせます。
たとえば5つ使いたければ次のように書きます。
function example<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E): [A, B, C, D, E] {
return [a, b, c, d, e];
}
const result = example("apple", 42, true, new Date(), null);
ただし、パラメータが増えると読みづらくなるため、通常は2〜3個に留めるのが一般的です。
アルファベットの由来と使い方
型パラメータはアルファベットなら自由に付けられますが、次のような慣例があります。
パラメータ | 意味の由来 |
---|---|
T | Type(型) |
U | Typeの2番目(特に意味なし) |
K | Key(キー) |
V | Value(値) |
E | Element(要素) |
読みやすさのため、通常は短いアルファベット1文字で記述します。必要に応じてわかりやすい単語 (KeyType
, ValueType
など) を使っても問題ありません。
これを知っておくだけで、かなり馴染みやすくなりますね!
オブジェクト型にもジェネリクス
関数だけでなく、インターフェース(オブジェクトの形)にも使えます。
interface ApiResponse<T> {
status: number;
data: T;
}
// Userの情報を受け取る例
type User = { id: number; name: string };
const response: ApiResponse<User> = {
status: 200,
data: { id: 1, name: "Alice" }
};
サーバーから返ってくるデータの形が変わっても、ApiResponse
はそのまま使い回せます。
型に「一部の条件」をつけたいとき
たとえば「長さを調べられるオブジェクトだけを受け取りたい」ときは、型に制約(constraint)をつけます。
// lengthプロパティがあることを示すひな形
type HasLength = { length: number };
function logLength<T extends HasLength>(value: T): void {
console.log("長さは", value.length);
}
logLength("Hello"); // OK(文字列はlength持ってます)
logLength([1,2,3]); // OK(配列もlengthあります)
// logLength(123); // NG(数値にlengthはありません)
T extends HasLength
の部分で「TはHasLengthの仲間ね」と伝えています。
デフォルトの型を用意する
何度も同じ型を指定するのが面倒なら、初めから「これがデフォルトだよ」と決めておくことも可能です。
type Maybe<T = string> = T | null;
const x: Maybe = "ABC"; // Tが省略されたのでstring扱い
const y: Maybe<number> = 123; // numberを指定もOK
クラスでの例:Stack
後から型を決められるクラス、というイメージで書くと…
class Stack<T> {
private items: T[] = [];
push(item: T) {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const stack = new Stack<number>();
stack.push(10);
console.log(stack.pop()); // 数値が返ってきます
中身が何かに応じて使い分けられる、再利用性バツグンの設計です。
よくあるつまずきポイント
- ジェネリクスが複雑すぎる→最初はひとつずつ理解し、慣れたら増やしましょう。
- 型を指定し忘れてエラー→説明をよく読んで、
extends
や<T>
の位置をチェック。 - 任意の型を使いたいときは
unknown
と組み合わせ→まずは型をunknown
で受け取り、関数内でチェックすると安心です。
まとめ
ジェネリクスは最初は少し難しく感じるかもしれませんが、使いこなすとコードがシンプルかつ安全になります。今回ご紹介した基本パターンをおさらいしながら、ぜひ自分のプロジェクトで試してみてください。ひとつずつ理解を深めるうちに、いつの間にか手放せない便利機能になっているはずです!

大阪のエンジニアが書いているブログ。
コメント