TypeScriptのジェネリクスを図解で理解する

プログラミング言語

TypeScriptのジェネリクスは、型を引数として受け取る仕組みです。「同じロジックを複数の型で安全に再利用」できる強力な機能ですが、初学者にとってはとっつきにくい部分でもあります。本記事では、ジェネリクスを図解的に理解できるよう解説します。

ジェネリクスとは

関数や型に「型変数」を持たせ、使う側が具体的な型を指定できる仕組みです。Tがよく使われますが、任意の名前で構いません。

function identity<T>(value: T): T {
  return value;
}

identity<string>('hello'); // T = string
identity<number>(42);      // T = number
identity('hello');         // 推論で T = string

配列を扱う例

function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

const n = first([1, 2, 3]);  // number | undefined
const s = first(['a', 'b']); // string | undefined

制約(extends)

「Tはこの形を持つ型に限る」と制約をかけられます。

function getId<T extends { id: number }>(obj: T): number {
  return obj.id;
}

getId({ id: 1, name: 'A' }); // OK
// getId({ name: 'A' });     // エラー:idが無い

複数の型変数

function merge<A, B>(a: A, b: B): A & B {
  return { ...a, ...b };
}

const merged = merge({ name: 'A' }, { age: 30 });
// { name: string; age: number }

クラスでのジェネリクス

class Box<T> {
  constructor(public value: T) {}
}
const numBox = new Box(42);     // Box<number>
const strBox = new Box('hello'); // Box<string>

条件付き型と組み合わせ

type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<'hello'>; // 'yes'
type B = IsString<42>;       // 'no'

keyof と組み合わせる

function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: 'A' };
const n = getProp(user, 'id');    // number
const s = getProp(user, 'name');  // string

実務でよく出るパターン

  • API呼び出しのレスポンス型:fetch<T>(url: string): Promise<T>
  • Reactフック:useState<User>() useReducer<Reducer<State, Action>>()
  • コレクション操作:map・filter・reduce
  • ファクトリ・ビルダー:型に応じて返り値を変える

まとめ

ジェネリクスは「型を後から決められる関数・型」を作る仕組みです。identity関数→配列→制約→keyofの順で段階的に学べば、難しさは大きく減ります。実務では「型を保ったまま再利用できる」恩恵が大きく、避けて通れないテーマです。