T-CREATOR

<div />

TypeScript Genericsの使用例を早見表でまとめる 記法と頻出パターンを整理

2026年1月23日
TypeScript Genericsの使用例を早見表でまとめる 記法と頻出パターンを整理

TypeScript のジェネリクスは「型安全」と「再利用性」を両立させる強力な仕組みです。しかし、記法が独特なため「読めるけど書けない」「どの場面で使うべきかわからない」と感じる方も多いのではないでしょうか。

この記事では、実務で頻出するジェネリクスパターンを早見表(チートシート)形式で整理し、型推論を活かした安全なコードを「読める・書ける」ようになることを目指します。初学者から実務者まで、それぞれの判断材料となる内容を盛り込みました。

ジェネリクス記法チートシート早見表

#記法機能ユースケース型安全への貢献
1<T>型パラメータ宣言汎用関数・汎用コンポーネント呼び出し時に型が確定し補完が効く
2<T, U>複数パラメータデータ結合・変換処理入出力の型を個別に追跡できる
3<T extends X>型制約(プロパティ保証)特定メソッド・プロパティ必須の処理存在しないプロパティアクセスを防止
4<K extends keyof O>キー制約オブジェクトの安全なプロパティ取得存在しないキー指定をコンパイルエラーに
5<T = Default>デフォルト型後方互換・オプション引数既存コードを壊さず段階的に型を絞れる
6条件型 T extends X ? A : B型分岐成功/失敗の型分離分岐後の型が確定し誤アクセスを防止
7infer型抽出関数引数・戻り値の取り出しフレームワーク連携時の型安全を確保
8マップ型 { [K in keyof T]: ... }プロパティ一括変換readonly 化・Optional 化既存型から派生型を安全に生成

各パターンの詳細は後述します。

検証環境

  • OS: macOS Sonoma 15.2
  • Node.js: 22.13.0
  • TypeScript: 5.7.3
  • 主要パッケージ:
    • React: 19.0.0
    • @types/react: 19.0.7
  • 検証日: 2026 年 01 月 23 日

ジェネリクスが必要になる背景

ジェネリクス(Generics)とは、型をパラメータとして受け取る仕組みです。関数やクラスを定義する時点では型を決めず、使う側が型を指定することで、1 つの実装を複数の型で再利用できます。

TypeScript で型安全なコードを書こうとすると、ある問題に直面します。それは「汎用的な処理を書きたいが、any を使うと型推論の恩恵を失う」というジレンマです。

以下の図は、ジェネリクスを使わない場合と使う場合の型情報の流れを示しています。

mermaidflowchart LR
  subgraph without["any を使う場合"]
    A1["入力: any"] --> B1["処理"]
    B1 --> C1["出力: any"]
    C1 --> D1["型情報が失われる"]
  end
  subgraph with["ジェネリクスを使う場合"]
    A2["入力: T"] --> B2["処理"]
    B2 --> C2["出力: T"]
    C2 --> D2["型情報が保持される"]
  end

上図のように、any を使うと入力から出力まで型情報が失われますが、ジェネリクスを使えば型情報が保持され、型推論が正しく機能します。

実務では以下のような場面でジェネリクスが活躍します。

  • API レスポンスの型付け: fetch ラッパで戻り値の型を呼び出し側が指定
  • React コンポーネント: リストやフォームなど、扱うデータ型が異なる汎用 UI
  • ユーティリティ関数: 配列操作やオブジェクト変換など、型を問わない処理

つまずきやすい点: ジェネリクスは「テンプレート」や「型変数」と呼ばれることもあります。C++ のテンプレートや Java のジェネリクスと似た概念ですが、TypeScript は構造的型付けのため挙動が異なる部分もあります。

any を使った場合に起きる課題

ジェネリクスを使わずに any で汎用関数を書くと、以下のような問題が発生します。

この章では、any を使った場合の問題点を具体的なコードで確認します。

typescript// any を使った汎用関数(非推奨)
function getFirst(arr: any[]): any {
  return arr[0];
}

const numbers = [1, 2, 3];
const first = getFirst(numbers);
// first の型は any → 補完が効かない、typo も検出されない
console.log(first.toUpperCase()); // 実行時エラー! number に toUpperCase はない

実際に業務で問題になったケースを紹介します。あるプロジェクトで API レスポンスを any で扱っていたところ、バックエンド側のフィールド名変更に気づかず、本番環境でエラーが発生しました。TypeScript を使っているにもかかわらず、型チェックが機能していなかったのです。

この問題を放置すると、以下のリスクがあります。

  • IDE 補完が機能しない: プロパティ名の typo に気づけない
  • リファクタリングが困難: 影響範囲を追跡できない
  • 実行時エラーの増加: 型の不整合がコンパイル時に検出されない

ジェネリクスを使えば、これらの問題を解決できます。

ジェネリクス導入による解決と判断基準

この章では、ジェネリクスを採用する判断基準と、採用しなかった代替案について整理します。

採用した設計

ジェネリクスを採用する基準は「同じ処理を複数の型で使い回す必要があるか」です。以下の条件に当てはまる場合、ジェネリクスが有効です。

  1. 戻り値の型が引数の型に依存する
  2. 複数の型で同じロジックを使いたい
  3. 型情報を呼び出し側まで伝搬させたい
typescript// ジェネリクスを使った汎用関数(推奨)
function getFirst<T>(arr: T[]): T | undefined {
  return arr[0];
}

const numbers = [1, 2, 3];
const first = getFirst(numbers);
// first の型は number | undefined → 補完が効く
// first.toUpperCase(); // コンパイルエラー! number に toUpperCase はない

採用しなかった代替案

以下のアプローチは検討しましたが、採用しませんでした。

代替案不採用の理由
any を使う型安全性が失われ、TypeScript を使う意味がなくなる
unknown を使う使用側で毎回型ガードが必要になり煩雑
オーバーロードで対応型ごとに定義が必要で保守コストが高い
Union 型で列挙想定外の型を追加するたびに修正が必要

つまずきやすい点: unknown は型安全な any として紹介されることがありますが、ジェネリクスとは用途が異なります。unknown は「型が不明だが安全に扱いたい」場合、ジェネリクスは「型を呼び出し側で決めたい」場合に使います。

頻出パターンの具体例

この章では、チートシート早見表で紹介した 8 つのパターンを、実際のコードで詳しく解説します。

パターン 1: 型パラメータ宣言

型パラメータ(Type Parameter)とは、関数やクラスを定義する際に「後から決まる型」を表す変数です。T は慣例的な名前で、Type の頭文字から来ています。

<T> は最も基本的なジェネリクス記法です。呼び出し時に型が確定し、型推論によって自動的に型が決まります。

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

// 型推論により T = number と自動判定
const num = identity(42);

// 明示的に型を指定することも可能
const text = identity<string>("Hello");

検証の結果、ほとんどのケースで型推論に任せた方がコードが簡潔になります。明示的な型指定は、型推論が期待通りに働かない場合にのみ使用します。

パターン 2: 複数パラメータ <T, U>

複数の型パラメータを使うと、入力と出力で異なる型を扱えます。データの結合や変換処理で頻出するパターンです。

typescriptfunction merge<T, U>(a: T, b: U): T & U {
  return { ...a, ...b };
}

const user = merge({ id: 1 }, { name: "Sato" });
// user の型: { id: number } & { name: string }
// → { id: number; name: string } として扱える

交差型(Intersection Type)T & U は「両方のプロパティを持つ型」を表します。型推論が交差まで追跡するため、結合後も IDE 補完が正しく機能します。

パターン 3: 型制約

extends 制約とは、型パラメータが満たすべき条件を指定する仕組みです。「T は少なくとも X の構造を持つ」という制約を課せます。

typescriptfunction toLabel<T extends { label: string }>(obj: T): string {
  return obj.label.toUpperCase();
}

// OK: label プロパティを持つオブジェクト
toLabel({ label: "book", pages: 200 });

// コンパイルエラー: label プロパティがない
// toLabel({ pages: 10 });

実際に試したところ、この制約により実行時の undefined チェックが不要になり、ロジックがシンプルになりました。

パターン 4: キー制約

keyof 演算子はオブジェクト型からキーの Union 型を取り出します。これと組み合わせることで、存在しないキーの指定をコンパイル時に検出できます。

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

const point = { x: 10, y: 20 };

// OK: 存在するキー
const x = prop(point, "x"); // 型: number

// コンパイルエラー: 存在しないキー
// const z = prop(point, 'z');

業務で問題になった経験として、キー名の typo がレビューで見逃され、本番でエラーになったことがあります。この制約を使えば、そのようなバグをコンパイル時に防げます。

パターン 5: デフォルト型 <T = Default>

デフォルト型を設定すると、型パラメータを省略した場合にデフォルト値が使われます。既存の API を壊さずに型パラメータを追加できるため、後方互換性の維持に役立ちます。

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

// 型パラメータを省略 → T は unknown
const resp1: ApiResponse = { ok: true, data: { id: 1 } };

// 型パラメータを指定 → T は { id: number }
const resp2: ApiResponse<{ id: number }> = { ok: true, data: { id: 1 } };

つまずきやすい点: デフォルト型に any を使うと型安全性が失われます。unknown を使えば、使用側で型を絞り込む必要があるため、安全性を保てます。

パターン 6: 条件型 T extends X ? A : B

条件型(Conditional Types)とは、型レベルで条件分岐を行う仕組みです。「型で if 文を書く」とイメージするとわかりやすいでしょう。

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

// T = number の場合
type Ok = Result<number>;
// → { success: true; value: number }

// T = Error の場合
type Ng = Result<Error>;
// → { success: false; error: Error }

API レイヤーで成功/失敗を型レベルで分離できるため、使う側で success フラグを見れば型が自動的に絞り込まれます。

以下の図は、条件型による型分岐の流れを示しています。

mermaidflowchart TD
  input["入力型 T"] --> check{"T extends Error?"}
  check -->|Yes| error["{ success: false, error: T }"]
  check -->|No| success["{ success: true, value: T }"]

条件型を使うことで、型レベルで成功と失敗を明確に分離できます。

パターン 7: 型抽出 infer

infer キーワードは、条件型の中で「マッチした部分を取り出す」ために使います。関数の引数型や戻り値型を抽出する際に重宝します。

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

// 関数型から第一引数の型を抽出
type A = FirstArg<(x: number, y: string) => void>;
// → number

type B = FirstArg<() => void>;
// → never(引数がないため)

フレームワークのヘルパー型を作る際、受け取る関数のシグネチャを解析するのに活用できます。TypeScript 標準の Parameters<T>ReturnType<T> も内部で infer を使っています。

パターン 8: マップ型 { [K in keyof T]: ... }

マップ型(Mapped Types)とは、既存の型のプロパティを一括で変換する仕組みです。readonly 化や Optional 化など、型の派生に使います。

typescripttype ReadonlyDeep<T> = {
  readonly [K in keyof T]: T[K] extends object ? ReadonlyDeep<T[K]> : T[K];
};

type Config = {
  db: {
    host: string;
    port: number;
  };
};

type FrozenConfig = ReadonlyDeep<Config>;
// すべてのプロパティが readonly になる

ライブラリを公開する際、「設定オブジェクトは不変」という制約を型レベルで保証できます。

実務での活用例

この章では、React コンポーネントと API ラッパーという実務で頻出する 2 つのケースを紹介します。

React 汎用リストコンポーネント

ジェネリクスを使うと、扱うデータ型に依存しない汎用コンポーネントを作成できます。

tsxtype ListProps<T> = {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
};

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// 使用例
type User = { id: number; name: string };

const users: User[] = [
  { id: 1, name: "Sato" },
  { id: 2, name: "Suzuki" },
];

<List
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
  keyExtractor={(user) => user.id}
/>;

呼び出しごとに T が決まるため、renderItem 内で user.name にアクセスする際も型推論が効きます。

型安全な fetch ラッパー

API からデータを取得する際、戻り値の型を呼び出し側で指定できるようにすると、型安全な API クライアントを構築できます。

typescriptasync function fetchJson<T>(url: string): Promise<T> {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`HTTP Error: ${response.status}`);
  }

  return response.json() as Promise<T>;
}

// 使用例
type Post = {
  id: number;
  title: string;
  body: string;
};

const post = await fetchJson<Post>("/api/posts/1");
// post の型は Post → IDE 補完が効く
console.log(post.title);

検証の結果、このパターンを採用したプロジェクトでは、API レスポンスの型不整合によるバグが大幅に減少しました。

つまずきやすい点: as T によるキャストは、実行時の型チェックを行いません。バックエンドとの型定義を共有するか、Zod などのバリデーションライブラリと組み合わせることを推奨します。

ジェネリクスパターン詳細比較まとめ

この章では、各パターンの使い分けを詳細な比較表で整理します。

パターン記法例向いているケース向かないケース型安全への貢献度
型パラメータ <T>function id<T>(v: T): T単純な汎用処理型に依存した処理分岐★★★
複数パラメータ <T, U>function merge<T, U>(a: T, b: U)データ結合・変換単一型で完結する処理★★★
型制約 <T extends X><T extends { label: string }>特定プロパティ必須の処理制約なしで動作する処理★★★★
キー制約 <K extends keyof O><O, K extends keyof O>安全なプロパティアクセス動的キー生成★★★★★
デフォルト型 <T = X>type Box<T = string>後方互換・段階的導入常に明示指定が必要な場合★★
条件型T extends X ? A : B型レベルの分岐処理単純な型定義★★★★
inferF extends (arg: infer P) => any ? P : never型の部分抽出単純な型変換★★★
マップ型{ [K in keyof T]: T[K] }プロパティ一括変換単一プロパティの変更★★★★

選択のフローチャート

どのパターンを使うべきか迷った場合は、以下の順で検討します。

mermaidflowchart TD
  start["ジェネリクスが必要?"] --> q1{"複数の型で<br/>同じ処理を使う?"}
  q1 -->|No| none["ジェネリクス不要"]
  q1 -->|Yes| q2{"型に制約が必要?"}
  q2 -->|No| basic["基本の <T> を使用"]
  q2 -->|Yes| q3{"オブジェクトのキー?"}
  q3 -->|Yes| keyof["keyof 制約を使用"]
  q3 -->|No| extends["extends 制約を使用"]

まず基本の <T> から始め、必要に応じて制約を追加するのが実務的なアプローチです。

まとめ

TypeScript のジェネリクスは、型安全性と再利用性を両立させる強力な機能です。

ただし、すべての場面でジェネリクスを使うべきではありません。「同じ処理を複数の型で使い回す必要があるか」を判断基準とし、過度に複雑な型を作らないことが重要です。

今回紹介した 8 つのパターンは、それぞれ異なるユースケースに対応しています。

  • 基本の <T> から始める
  • 必要に応じて extends で制約を追加する
  • keyof でキーの安全性を確保する
  • 条件型や infer は高度なユースケースで活用する

まずは基本パターンから実務に取り入れ、徐々に応用パターンへステップアップすることをおすすめします。このチートシート早見表を手元に置いて、ジェネリクスを「読める・書ける」ようになるための参考としてご活用ください。

関連リンク

著書

とあるクリエイター

フロントエンドエンジニア Next.js / React / TypeScript / Node.js / Docker / AI Coding

;