- TypeScript
- 技術 💻
TypeScriptジェネリクスの使い方|基本から実務パターンまでエラー文つき
TypeScriptのジェネリクスとは、型を「引数」として受け取り、呼び出すたびに具体的な型を当てはめられる仕組みです。 関数・型・クラスを1つ書くだけで、number用・string用などを作り分けずに、しかも型安全に使い回せます。<T> の T は「あとで決まる型の名前」と考えると理解しやすくなります。
この記事では、2026年9月時点のnpm最新版 TypeScript 7.0.2 で実際に tsc を実行し、出たエラー文をそのまま載せています。 any版→オーバーロード→ジェネリクスと書き換えながら「なぜジェネリクスが必要か」を確認し、extends 制約、keyof、デフォルト型引数、const 型パラメータ、NoInfer、条件型と infer、そしてAPIレスポンス型やResult型などの実務パターンまで順に解説します。
TypeScriptジェネリクスの早見表
| やりたいこと | 書き方 | 使える版の目安 |
|---|---|---|
| 型を引数にする | function f<T>(x: T): T |
初期から |
| 型に条件をつける | <T extends { length: number }> |
初期から |
| オブジェクトのキーに限定 | <T, K extends keyof T> / 戻り値 T[K] |
2.1〜 |
| 型引数に既定値 | type R<T = unknown> |
2.3〜 |
| 条件で型を切り替え | T extends string ? A : B |
2.8〜 |
| 型の一部を取り出す | T extends Promise<infer V> ? V : T |
2.8〜 |
| リテラル型のまま推論 | <const T extends readonly string[]> |
5.0〜 |
| 推論に使わせない | NoInfer<T> |
5.4〜 |
| よく使う型変換 | Partial Pick Omit Record ReturnType Awaited |
組み込み |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScriptジェネリクスの使い方|基本から実務パターンまでエラー文つき」
検証環境とtsconfig
この記事のコードは、次の環境で npx tsc -p . を実行して確認しました。
- TypeScript 7.0.2(
npm i typescriptで入った2026年9月時点の最新版。Goで書き直されたネイティブ版コンパイラ) - Node.js 22.22.2
{
"compilerOptions": {
"strict": true,
"target": "es2022",
"module": "esnext",
"noEmit": true
},
"include": ["src"]
}
TypeScript 7.0 の公式発表では、strict が既定で true になったと案内されています。明示しておくと、どの版でも同じ結果になるので安心です。
なぜジェネリクスが必要か:any版→オーバーロード→ジェネリクス
「配列の先頭要素を返す関数」を例に、3通りの書き方を比べます。
1. any版:型の情報が失われる
function firstAny(arr: any[]): any {
return arr[0];
}
const n = firstAny([1, 2, 3]);
n.toUpperCase(); // number なのにエラーにならない
tsc はエラーを出しません。 戻り値が any になるため、数値に toUpperCase() を呼んでも通ってしまいます。同じ処理をJavaScriptとして実行すると、実行時に次のエラーになります。
TypeError: n.toUpperCase is not a function
any は型チェックを止めるので、ミスが実行時まで見つかりません。
2. オーバーロード版:型ごとに宣言が増える
function first(arr: number[]): number | undefined;
function first(arr: string[]): string | undefined;
function first(arr: (number | string)[]): number | string | undefined {
return arr[0];
}
const a = first([1, 2, 3]);
const b = first(["x", "y"]);
const c = first([true, false]);
src/a2_overload.ts(8,18): error TS2769: No overload matches this call.
The last overload gave the following error.
Type 'boolean' is not assignable to type 'string'.
型は守られますが、boolean用、Date用…と対応する型ごとに宣言を書き足す必要があります。
3. ジェネリクス版:1つの定義で全部の型に対応
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const a = first([1, 2, 3]); // number | undefined
const b = first(["x", "y"]); // string | undefined
const c = first([true, false]); // boolean | undefined
const d = first<string>([1, 2]);
a?.toUpperCase();
src/a3_generic.ts(7,26): error TS2322: Type 'number' is not assignable to type 'string'.
src/a3_generic.ts(7,29): error TS2322: Type 'number' is not assignable to type 'string'.
src/a3_generic.ts(8,4): error TS2339: Property 'toUpperCase' does not exist on type 'number'.
呼び出し時の引数から T が自動で推論され、戻り値の型も正しく決まります。 first<string>(...) のように明示もできます。
| 書き方 | 型安全 | 書く量 | 新しい型への対応 |
|---|---|---|---|
| any | なし | 少ない | 何でも通る(ミスも通る) |
| オーバーロード | あり | 型の数だけ増える | 宣言を追加 |
| ジェネリクス | あり | 少ない | 自動で対応 |
extends制約:型パラメータに条件をつける
T には何でも入るため、そのままでは T のプロパティを使えません。
function bad<T>(x: T) {
return x.length;
}
src/a4_extends.ts(8,12): error TS2339: Property 'length' does not exist on type 'T'.
extends で「最低限この形を持つ型」と条件をつけると、そのプロパティが使えるようになります。
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("abc", "de"); // OK: string は length を持つ
longest([1, 2], [3]); // OK: 配列も length を持つ
longest(10, 20);
src/a4_extends.ts(6,9): error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
ここでの extends は「継承」ではなく「〜に代入できる型に限る」という意味です。
keyofとインデックスアクセス型:キー名を型で守る
keyof T は「T のキー名のユニオン型」、T[K] は「T のキー K の値の型」です。 組み合わせると、存在するキーだけを受け付け、戻り値の型も正確な関数が書けます。
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "Sato", active: true };
const id = getProp(user, "id"); // number
const name = getProp(user, "name"); // string
getProp(user, "email");
src/a5_keyof.ts(7,15): error TS2345: Argument of type '"email"' is not assignable to parameter of type '"active" | "id" | "name"'.
存在しないキーはコンパイル時に見つかり、エラー文に使えるキーの一覧まで出ます。 エディタの補完にも候補が表示されるので、キー名の打ち間違いが起きにくくなります。
デフォルト型引数:省略時の型を決める
関数の引数と同じく、型パラメータにも既定値をつけられます。
type ApiResponse<T = unknown, E = string> = {
data: T | null;
error: E | null;
};
const r1: ApiResponse = { data: 1, error: null }; // T = unknown
const r2: ApiResponse<{ id: number }> = { data: { id: 1 }, error: null };
const r3: ApiResponse<{ id: number }> = { data: { id: "1" }, error: null };
src/a6_default.ts(7,51): error TS2322: Type 'string' is not assignable to type 'number'.
既定値を any ではなく unknown にしておくと、型を指定し忘れたときも安全側に倒れます。
const型パラメータ:リテラル型のまま推論する(5.0〜)
通常、配列リテラルを渡すと string[] のように広い型で推論されます。 型パラメータに const をつけると、as const を書かなくても readonly ["/", "/about"] のような細かい型で推論されます。
function routes<T extends readonly string[]>(paths: T): T { return paths; }
function routesConst<const T extends readonly string[]>(paths: T): T { return paths; }
const r1 = routes(["/", "/about"]); // string[]
const r2 = routesConst(["/", "/about"]); // readonly ["/", "/about"]
type R1 = typeof r1;
type R2 = typeof r2;
const check1: R1 = ["/x"]; // OK (string[])
const check2: R2 = ["/x"];
src/a7_const.ts(8,21): error TS2322: Type '"/x"' is not assignable to type '"/"'.
R1 には何の文字列でも入りますが、R2 は渡した値そのものの型になっています。 ルート定義や設定キーの一覧など、値を型として使いたいときに便利です。
NoInfer:その引数からは推論させない(5.4〜)
型パラメータは、複数の引数から推論された候補を合わせて決まります。 そのため「初期値」のような引数に想定外の値を渡すと、型が広がってエラーにならないことがあります。
function createStore<S extends string>(states: S[], initial: S) {
return { states, initial };
}
function createStore2<S extends string>(states: S[], initial: NoInfer<S>) {
return { states, initial };
}
createStore(["idle", "loading"], "done"); // エラーにならない(S に "done" も含まれる)
createStore2(["idle", "loading"], "done");
src/a8_noinfer.ts(8,35): error TS2345: Argument of type '"done"' is not assignable to parameter of type '"idle" | "loading"'.
NoInfer<S> をつけた引数は推論の材料にならず、states から決まった型でチェックされます。
条件型とinfer入門
条件型は T extends U ? X : Y の形で、「T が U に当てはまるなら X、そうでなければ Y」を表します。 infer を使うと、当てはまった型の一部を取り出せます。
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<"hi">; // "yes"
type B = IsString<42>; // "no"
type C = IsString<string | number>; // "yes" | "no"(ユニオンは1つずつ分配される)
type ElementOf<T> = T extends readonly (infer U)[] ? U : never;
type D = ElementOf<string[]>; // string
type Awaited2<T> = T extends Promise<infer V> ? V : T;
type E = Awaited2<Promise<number>>; // number
async function fetchUser() { return { id: 1, name: "Sato" }; }
type User = Awaited<ReturnType<typeof fetchUser>>;
const x: C = "maybe";
const u: User = { id: 1 };
src/a9_cond.ts(11,7): error TS2322: Type '"maybe"' is not assignable to type 'C'.
src/a9_cond.ts(12,7): error TS2741: Property 'name' is missing in type '{ id: number; }' but required in type '{ id: number; name: string; }'.
Awaited<ReturnType<typeof 関数>> は、非同期関数の戻り値の型を取り出す定番の組み合わせです。 関数を直せば型も自動で追従するので、型定義を二重に書かずに済みます。
| 構文 | 意味 | 例 |
|---|---|---|
T extends U ? X : Y |
条件で型を選ぶ | IsString<T> |
infer V |
当てはまった部分を変数Vとして取り出す | Promise<infer V> |
| 分配 | ユニオンを1つずつ評価する | IsString<string | number> |
never |
「該当なし」 | ユニオンから消える |
ユーティリティ型:組み込みのジェネリック型
TypeScriptには、ジェネリクスで作られた便利な型が最初から用意されています。
| 型 | 働き | 使いどころ |
|---|---|---|
Partial<T> |
全プロパティを省略可能に | 更新用の入力 |
Required<T> |
全プロパティを必須に | 設定の既定値を埋めた後 |
Readonly<T> |
全プロパティを読み取り専用に | 状態オブジェクト |
Pick<T, K> |
指定キーだけ残す | 一覧表示用の型 |
Omit<T, K> |
指定キーを除く | 自動採番のidを除く |
Record<K, V> |
キーKで値Vのオブジェクト | 対応表 |
ReturnType<F> |
関数の戻り値の型 | 関数から型を作る |
Parameters<F> |
関数の引数の型(タプル) | ラッパー関数 |
Awaited<T> |
Promiseの中身の型 | 非同期関数の結果 |
type User = { id: number; name: string; email: string; createdAt: Date };
type UserUpdate = Partial<Omit<User, "id" | "createdAt">>;
type UserSummary = Pick<User, "id" | "name">;
type RoleMap = Record<"admin" | "member", UserSummary[]>;
const patch: UserUpdate = { name: "Suzuki" };
const bad: UserUpdate = { id: 2 };
const roles: RoleMap = { admin: [], member: [{ id: 1, name: "Sato" }] };
src/b3_util.ts(6,27): error TS2353: Object literal may only specify known properties, and 'id' does not exist in type 'Partial<Omit<User, "createdAt" | "id">>'.
元の User 型を1つ直すだけで、派生した型すべてに変更が反映されます。
実務パターン
APIレスポンス型を共通化する
type ApiResponse<T> = { data: T; fetchedAt: string };
type User = { id: number; name: string };
type Todo = { id: number; title: string; done: boolean };
async function getJson<T>(url: string): Promise<ApiResponse<T>> {
// 本来は fetch(url) を呼ぶ。ここではダミーデータを返す
const dummy: Record<string, unknown> = {
"/users/1": { id: 1, name: "Sato" },
"/todos/1": { id: 1, title: "write article", done: false },
};
return { data: dummy[url] as T, fetchedAt: new Date(0).toISOString() };
}
const u = await getJson<User>("/users/1");
console.log(u.data.name);
const t = await getJson<Todo>("/todos/1");
console.log(t.data.title, t.data.done);
Node.js 22.22.2 で実行した結果です。
Sato
write article false
注意点として、getJson<User> の User は「そうであると信じる」宣言で、実行時にデータの形は確認されません。 外部から来るデータは、zodなどのスキーマ検証ライブラリで確かめてから型をつけると安全です。
Result型で成功と失敗を型で表す
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
function ok<T>(value: T): Result<T, never> { return { ok: true, value }; }
function err<E>(error: E): Result<never, E> { return { ok: false, error }; }
function parseAge(input: string): Result<number, string> {
const n = Number(input);
return Number.isInteger(n) && n >= 0 ? ok(n) : err(`invalid age: ${input}`);
}
for (const s of ["42", "abc"]) {
const r = parseAge(s);
if (r.ok) {
console.log("ok", r.value + 1); // ここでは r.value が number
} else {
console.log("ng", r.error); // ここでは r.error が string
}
}
ok 43
ng invalid age: abc
r.ok を確認すると、分岐の中で value と error のどちらが使えるかをTypeScriptが判断してくれます。 失敗の可能性を戻り値の型に表せるので、エラー処理の書き忘れに気づきやすくなります。
型安全なイベントバス(ジェネリッククラス)
class TypedEventBus<Events extends Record<string, unknown>> {
private handlers: { [K in keyof Events]?: ((p: Events[K]) => void)[] } = {};
on<K extends keyof Events>(type: K, h: (p: Events[K]) => void) {
(this.handlers[type] ??= []).push(h);
}
emit<K extends keyof Events>(type: K, payload: Events[K]) {
this.handlers[type]?.forEach((h) => h(payload));
}
}
const bus = new TypedEventBus<{ login: { userId: number }; logout: undefined }>();
bus.on("login", (p) => console.log("login", p.userId));
bus.emit("login", { userId: 7 });
bus.emit("login", { userId: "7" });
src/b5_class.ts(13,21): error TS2322: Type 'string' is not assignable to type 'number'.
イベント名ごとにペイロードの型が決まるので、送る側と受ける側の食い違いを防げます。
Reactのカスタムフックの型(型定義のみ)
実装は省き、型だけを示します。引数の関数から戻り値と引数の型を受け継ぐ例です。
type AsyncState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: unknown };
declare function useAsync<T, Args extends unknown[]>(
fn: (...args: Args) => Promise<T>,
): [state: AsyncState<T>, run: (...args: Args) => void];
declare function fetchUser(id: number): Promise<{ id: number; name: string }>;
const [state, run] = useAsync(fetchUser);
run(1);
run("1");
if (state.status === "success") state.data.name;
src/b4_hook.ts(15,5): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
fetchUser を渡すだけで、run の引数は number、state.data は { id; name } と推論されます。
よくある失敗と直し方
| よくある失敗 | 起きること | こうするとよい |
|---|---|---|
とりあえず any にする |
ミスが実行時まで見つからない | <T> にして推論させる |
T のプロパティをそのまま使う |
TS2339 エラー | extends で必要な形を指定 |
キーを string で受ける |
存在しないキーも通る | K extends keyof T |
型引数の既定値を any にする |
指定し忘れに気づけない | 既定値は unknown |
| 使われない型パラメータを作る | 読み手が混乱する | 2か所以上で使う型だけを型パラメータにする |
| APIの型引数を検証の代わりにする | 実データと型がずれる | スキーマ検証と組み合わせる |
型パラメータは「入力と出力、または複数の引数の関係を表すため」に使うと覚えると、使いどころを判断しやすくなります。
よくある質問
Q. T 以外の名前を使ってもよいですか?
A. はい。慣習として T(Type)、K(Key)、V(Value)、E(Error)などの1文字がよく使われますが、TUser や Events のように意味のある名前にすると読みやすくなる場面もあります。
Q. 型引数は毎回書く必要がありますか?
A. 多くの場合は不要です。引数から推論されます。推論できない場合(戻り値だけに使う型など)や、意図を明示したい場合に f<User>() のように書きます。
Q. ジェネリクスとunion型はどう使い分けますか?
A. 入力と出力の型を連動させたいならジェネリクス、決まった数種類の値を受け付けるだけならunion型が向いています。first<T> は「渡した配列の要素の型を返す」という連動があるのでジェネリクスが適しています。
Q. TypeScript 7.0で書き方は変わりましたか?
A. この記事の範囲の構文はそのまま使えました。7.0はコンパイラがネイティブ化された版で、主な変更は strict の既定値などの設定面です。移行時は公式のリリース情報で設定の変更点を確認してください。
まとめ
- ジェネリクスは型を引数にして、1つの定義を型安全に使い回す仕組み
- any版は実行時エラーまで気づけず、オーバーロードは型の数だけ宣言が増える。ジェネリクスなら推論で自動対応
extendsで条件、keyofとT[K]でキー名と値の型を守る- デフォルト型引数は
unknownが安全側 const型パラメータ(5.0〜)でリテラル型のまま推論、NoInfer(5.4〜)で推論の材料から外す- 条件型と
infer、Awaited<ReturnType<typeof f>>で型を関数から導く - 実務では APIレスポンス型・Result型・型安全なイベント・フックの型に活躍する
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「TypeScriptジェネリクスの使い方|基本から実務パターンまでエラー文つき」
株式会社RJCでは、ジェネリクスのような型の設計を、実際のコードで試しながらメンバー同士で知見を交換しています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。