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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?