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 | 型分岐 | 成功/失敗の型分離 | 分岐後の型が確定し誤アクセスを防止 |
| 7 | infer | 型抽出 | 関数引数・戻り値の取り出し | フレームワーク連携時の型安全を確保 |
| 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 に気づけない
- リファクタリングが困難: 影響範囲を追跡できない
- 実行時エラーの増加: 型の不整合がコンパイル時に検出されない
ジェネリクスを使えば、これらの問題を解決できます。
ジェネリクス導入による解決と判断基準
この章では、ジェネリクスを採用する判断基準と、採用しなかった代替案について整理します。
採用した設計
ジェネリクスを採用する基準は「同じ処理を複数の型で使い回す必要があるか」です。以下の条件に当てはまる場合、ジェネリクスが有効です。
- 戻り値の型が引数の型に依存する
- 複数の型で同じロジックを使いたい
- 型情報を呼び出し側まで伝搬させたい
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 | 型レベルの分岐処理 | 単純な型定義 | ★★★★ |
infer | F 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は高度なユースケースで活用する
まずは基本パターンから実務に取り入れ、徐々に応用パターンへステップアップすることをおすすめします。このチートシート早見表を手元に置いて、ジェネリクスを「読める・書ける」ようになるための参考としてご活用ください。
関連リンク
著書
article2026年1月23日TypeScript Genericsの使用例を早見表でまとめる 記法と頻出パターンを整理
article2026年1月20日TypeScriptで関数型プログラミングを設計に取り入れる 純粋関数で堅牢にする手順
article2026年1月18日TypeScriptで非同期処理を型安全に書く使い方 Promiseとasync awaitの型定義を整理
article2026年1月16日TypeScriptの高度な型操作を使い方で理解する keyof typeof inferを実例で整理
article2026年1月16日TypeScriptでFunction Overloadsを設計に使う 柔軟なAPIパターンと使い分け
article2026年1月13日TypeScriptでHigher Kinded Typesを模倣する設計 ジェネリクスで関数型パターンを整理
article2026年1月23日TypeScriptのtypeとinterfaceを比較・検証する 違いと使い分けの判断基準を整理
article2026年1月23日TypeScript 5.8の型推論を比較・検証する 強化点と落とし穴の回避策
article2026年1月23日TypeScript Genericsの使用例を早見表でまとめる 記法と頻出パターンを整理
article2026年1月22日TypeScriptの型システムを概要で理解する 基礎から全体像まで完全解説
article2026年1月22日ZustandとTypeScriptのユースケース ストアを型安全に設計して運用する実践
article2026年1月22日TypeScriptでよく出るエラーをトラブルシュートでまとめる 原因と解決法30選
articleshadcn/ui × TanStack Table 設計術:仮想化・列リサイズ・アクセシブルなグリッド
articleRemix のデータ境界設計:Loader・Action とクライアントコードの責務分離
articlePreact コンポーネント設計 7 原則:再レンダリング最小化の分割と型付け
articlePHP 8.3 の新機能まとめ:readonly クラス・型強化・性能改善を一気に理解
articleNotebookLM に PDF/Google ドキュメント/URL を取り込む手順と最適化
articlePlaywright 並列実行設計:shard/grep/fixtures で高速化するテストスイート設計術
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来
