T-CREATOR

【早見表】TypeScript Generics(ジェネリクス)の使用例と記法まとめ

【早見表】TypeScript Generics(ジェネリクス)の使用例と記法まとめ

ジェネリクスを使えば 型安全再利用 を両立できます。
一度書いた処理をあらゆる型で安全に使い回せるため、開発の効率化とバグ回避を同時に叶えることができます。

この記事ではでは TypeScript ジェネリクス を中心に、早見表と具体的な活用術をまとめました。

ジェネリクス記法早見表

#シンタックス機能ユースケースサンプル
1<T>型パラメータ宣言汎用関数function id<T>(v:T):T
2<T, U>複数パラメータデータ結合type Pair<T,U>=…
3<T extends X>プロパティ保証toString 必須serialize<T extends {toString():string}>
4<K extends keyof O>キー限定安全なプロパティ取得prop<O,K extends keyof O>(o,k)
5デフォルト型 =…後方互換オプション引数ラッパtype Box<T=string>=…
6条件型型分岐API 成否判定type Res<T>=T extends Error?never:T
7infer部分抽出関数引数取り出しtype Arg<F>=F extends (...a:infer A)=>any?A:never
8マップ型プロパティ変換深い readonly 化type RO<T>={readonly[K in keyof T]:T[K]}

汎用関数

結論として <T> は「あとで決まる型」を予約する仕組みです。
ジェネリック関数は一つ書くだけで数値・文字列・独自型に自由対応します。

tsfunction identity<T>(value: T): T {
  return value;           // T が戻り値にも伝搬
}

const num  = identity(42);          // T = number
const text = identity<string>('Hi'); // T を明示指定

上記のように、呼び出し側で型が決まる ためキャスト不要で補完が効きます。

データ結合

複数パラメータを使うと異種オブジェクトを安全に合体できます。
T & U は交差型、つまり「両方のプロパティを持つ新型」です。

tsfunction merge<T, U>(a: T, b: U): T & U {
  return { ...a, ...b };  // 実際の処理は単純でも型情報は厳密
}

const user = merge({ id: 1 }, { name: 'Sato' });
// user: { id: number; name: string }

型推論が交差まで導き出すので、結合後も IDE 補完が失われません。

プロパティ保証

extends 制約は「これだけは必須」という安心網です。
実行時の undefined チェックを削れるのでロジックがスリムになります。

tsfunction toLabel<T extends { label: string }>(obj: T) {
  // label が必ず存在すると TypeScript が認識
  return obj.label.toUpperCase();
}

toLabel({ label: 'book', pages: 200 }); // OK
// toLabel({ pages: 10 });              // コンパイルエラー

複雑な型でも「最低限の形」を押さえるだけで汎用化が進みます。

キー限定

keyof と組み合わせれば「存在するキーだけ」を引数にできます。
綴りミスや仕様変更によるバグをコンパイル時に防げます。

tsfunction prop<O, K extends keyof O>(obj: O, key: K): O[K] {
  return obj[key];
}

prop({ x: 10, y: 20 }, 'x'); // 型安全
// prop({ x: 10 }, 'z');     // エラー: 'z' は存在しない

コードレビューで「キー間違い」を指摘するコストがゼロになります。

後方互換

デフォルト型は「パラメータ追加 → 既存コード崩壊」を防ぎます。

tstype ApiResponse<T = unknown> = {
  ok: boolean;
  data: T;
};

const resp: ApiResponse = { ok: true, data: { id: 1 } }; // T は unknown

過去の呼び出しに影響を与えず段階的に型を絞り込めます。

型分岐

条件型は「型で if 文を書く」イメージです。
API レイヤーで成功/失敗を厳密に分岐させられます。

tstype Result<T> = T extends Error
  ? { success: false; error: T }
  : { success: true; value: T };

type Ok = Result<number>; // { success: true; value: number }
type Ng = Result<Error>;  // { success: false; error: Error }

使う側は success を見て型が切り替わるため、誤アクセスが起きません。

部分抽出

infer は「型マッチングで拾った部分」を取り出すキーワードです。

tstype FirstArg<F> =
  F extends (arg: infer P, ...rest: any) => any ? P : never;

type A = FirstArg<(x: number) => void>; // number
type B = FirstArg<() => void>;          // never

フレームワークのヘルパーを作る際、受け取る関数のシグネチャを解析するのに重宝します。

プロパティ変換

マップ型を使うとネストしたプロパティへ一括操作できます。

tstype ReadonlyDeep<T> = {
  readonly [K in keyof T]: ReadonlyDeep<T[K]>;
};

type Conf = { db: { host: string; port: number } };
type Frozen = ReadonlyDeep<Conf>; // 深い階層まで readonly

ライブラリ公開時に「設定オブジェクトは不変」を保証したい場面で活躍します。

活用例

結論を先に述べるなら、ジェネリクスは「同じ処理を複数型で安全共有」する実装全般で効果的です。
以下では React と API ラッパに落とし込み、現場イメージを膨らませます。

React 汎用リスト

tsxtype ListProps<T> = { items: T[]; render: (item: T) => React.ReactNode };

function List<T>({ items, render }: ListProps<T>) {
  return <ul>{items.map(render)}</ul>;
}

ポイント

  1. 呼び出しごとに T が決まり 補完が変化
  2. DOM 構造は共有し デザイン修正が 1 箇所

fetch ラッパ

tsasync function fetchJson<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return (await res.json()) as T;
}

type Post = { id: number; title: string };
const post = await fetchJson<Post>('/api/post/1');

ポイント

  1. 戻り値の構造が確定 → UI 側で 型ガード不要
  2. レスポンス変更時は Post を更新するだけ

まとめ

ジェネリクスを活用すれば、実装量を抑えつつバグを減らし、読みやすい型付きコード実現することができます。
ぜひジェネリクス活用に役立ててください。

関連リンク

TypeScriptの記事Typescript