TypeScriptジェネリクス入門!一発で理解できる基本ガイド

typescript ジェネリクス TypeScript
この記事は約6分で読めます。

この記事の最終更新日: 2025年5月4日

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

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]

TU といったパラメータをカンマでつなげて、好きなだけ増やせます。(

型パラメータはいくつでも増やせるし、アルファベットなんでもOKだし、単語でも良い

TU といったパラメータはカンマで区切って、必要なだけ増やせます。

たとえば5つ使いたければ次のように書きます。

function example&lt;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個に留めるのが一般的です。

アルファベットの由来と使い方

型パラメータはアルファベットなら自由に付けられますが、次のような慣例があります。

パラメータ意味の由来
TType(型)
UTypeの2番目(特に意味なし)
KKey(キー)
VValue(値)
EElement(要素)

読みやすさのため、通常は短いアルファベット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で受け取り、関数内でチェックすると安心です。

まとめ

ジェネリクスは最初は少し難しく感じるかもしれませんが、使いこなすとコードがシンプルかつ安全になります。今回ご紹介した基本パターンをおさらいしながら、ぜひ自分のプロジェクトで試してみてください。ひとつずつ理解を深めるうちに、いつの間にか手放せない便利機能になっているはずです!

コメント

タイトルとURLをコピーしました