ジェネリクス — 型のパラメータ化
ジェネリクスは、型をパラメータとして受け取り、再利用可能なコンポーネントを作る仕組みです。 「具体的な型を後から決める」ことで、型安全性を保ちながら汎用的なコードを書けます。
// ジェネリクスなし — 型安全性が失われる
function firstElement(arr: any[]): any {
return arr[0];
}
// ジェネリクスあり — 型安全性を保持
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = firstElement([1, 2, 3]); // number | undefined
const str = firstElement(["a", "b", "c"]); // string | undefined
// 型パラメータ T が自動推論される 制約付きジェネリクス — extends で安全性を確保
// 制約なし — length プロパティの保証がない
function getLength<T>(arg: T): number {
// return arg.length; // エラー!T に length があるとは限らない
return 0;
}
// extends で制約を追加 — length を持つ型のみ受け入れる
function getLength<T extends { length: number }>(arg: T): number {
return arg.length; // OK! length の存在が保証される
}
getLength("hello"); // OK: string は length を持つ
getLength([1, 2, 3]); // OK: Array は length を持つ
// getLength(42); // エラー!number は length を持たない デフォルト型パラメータ
// デフォルト型パラメータ
interface ApiResponse<T = unknown> {
data: T;
status: number;
message: string;
}
// T を指定しない場合は unknown がデフォルト
const response: ApiResponse = { data: "anything", status: 200, message: "ok" };
// T を明示的に指定
const typedResponse: ApiResponse<string[]> = {
data: ["item1", "item2"],
status: 200,
message: "ok",
}; Conditional Types — 型の条件分岐
Conditional Types(条件型)は、三項演算子のような構文で型を条件分岐します。 TypeScript 2.8で導入され、型レベルプログラミングの基盤となる機能です。
// 基本構文: T extends U ? X : Y
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
type C = IsString<"hello">; // "yes" (リテラル型も string を拡張する) 分配条件型 — ユニオン型への自動適用
ユニオン型に対して条件型を適用すると、各メンバーに分配されるという重要な特性があります。
type ToArray<T> = T extends any ? T[] : never;
// ユニオン型に適用すると分配される
type Result = ToArray<string | number>;
// = (string extends any ? string[] : never) | (number extends any ? number[] : never)
// = string[] | number[]
// 注意: T[] とするだけでは (string | number)[] になる
type WithoutDistribution = (string | number)[]; // (string | number)[]
type WithDistribution = ToArray<string | number>; // string[] | number[] Mapped Types — 型の変換
Mapped Typesは、既存の型のプロパティを変換して新しい型を作る機能です。
keyof演算子と組み合わせて、型を体系的に変換します。
// 全プロパティをオプショナルにする(Partial の実装)
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// 全プロパティを読み取り専用にする(Readonly の実装)
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// 修飾子の除去: -? でオプショナルを外す
type Required<T> = {
[K in keyof T]-?: T[K];
};
// -readonly で読み取り専用を外す
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
}; Template Literal Types — 文字列型の合成
TypeScript 4.1で導入されたTemplate Literal Typesは、 文字列リテラル型をテンプレートで組み合わせる機能です。 イベント名やCSSプロパティ名など、パターン化された文字列の型定義に威力を発揮します。
// イベント名の型を自動生成
type EventName = `on${Capitalize<"click" | "focus" | "blur">}`;
// = "onClick" | "onFocus" | "onBlur"
// CSSプロパティのパターン
type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue = `${number}${CSSUnit}`;
// "100px", "1.5em", "2rem", "50%" などが有効
// HTTPエンドポイントの型安全なルーティング
type ApiRoute = `/api/${"users" | "posts" | "comments"}/${number}`;
// "/api/users/1", "/api/posts/42" などが有効 ユーティリティ型 — 標準搭載の型変換ツール
TypeScriptは頻出する型変換パターンをユーティリティ型として標準提供しています。 内部的にはMapped TypesやConditional Typesで実装されています。
| ユーティリティ型 | 効果 | 使用場面 |
|---|---|---|
Partial<T> | 全プロパティをオプショナルに | 更新APIの引数(一部だけ更新) |
Required<T> | 全プロパティを必須に | バリデーション後のデータ |
Readonly<T> | 全プロパティを読み取り専用に | 不変データの表現 |
Record<K, T> | キーK・値Tのオブジェクト型 | マッピングテーブル |
Pick<T, K> | 指定プロパティのみ抽出 | APIレスポンスの一部を使用 |
Omit<T, K> | 指定プロパティを除外 | 特定フィールドの非公開 |
Exclude<U, T> | ユニオンから特定型を除外 | エラー型の除外 |
Extract<U, T> | ユニオンから特定型を抽出 | 特定の型のみ取り出す |
NonNullable<T> | null/undefinedを除外 | nullチェック後の型 |
ReturnType<T> | 関数の戻り値型を取得 | 既存関数の型を再利用 |
Parameters<T> | 関数の引数型をタプルで取得 | ラッパー関数の定義 |
Awaited<T> | Promiseを再帰的にアンラップ | async関数の結果型 |
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
// 更新API — 全フィールドがオプショナル
type UpdateUser = Partial<User>;
// { id?: number; name?: string; email?: string; createdAt?: Date; }
// 公開プロフィール — emailとcreatedAtを除外
type PublicProfile = Pick<User, "id" | "name">;
// { id: number; name: string; }
// ID以外の作成時データ
type CreateUser = Omit<User, "id" | "createdAt">;
// { name: string; email: string; } 三種の神器 — as const / satisfies / infer
as const — リテラル型を保持する
as constは、値の型を最も具体的なリテラル型として推論させます。
配列はreadonlyタプルに、オブジェクトの各プロパティはリテラル型になります。
// as const なし — 型が拡大される
const colors = ["red", "green", "blue"];
// 型: string[]
// as const あり — リテラル型が保持される
const colors = ["red", "green", "blue"] as const;
// 型: readonly ["red", "green", "blue"]
// ユニオン型の自動生成
type Color = (typeof colors)[number]; // "red" | "green" | "blue"
// 設定オブジェクトに最適
const config = {
port: 3000,
host: "localhost",
debug: true,
} as const;
// 型: { readonly port: 3000; readonly host: "localhost"; readonly debug: true; } satisfies — 型検証しつつ推論を保持する
TypeScript 4.9で導入されたsatisfiesは、
型の適合性を検証しつつ、推論された型を保持する画期的な演算子です。
as(型アサーション)とは異なり、元の推論型が維持されます。
type ColorMap = Record<string, string | number[]>;
// satisfies: 型チェックしつつ、各プロパティの推論型を保持
const palette = {
red: "#ff0000",
green: [0, 255, 0],
} satisfies ColorMap;
palette.red.toUpperCase(); // ✅ OK: string型として推論維持
palette.green.map(x => x * 2); // ✅ OK: number[]型として推論維持
// as を使った場合は各プロパティが string | number[] になってしまう
const paletteWithAs = {
red: "#ff0000",
green: [0, 255, 0],
} as ColorMap;
// paletteWithAs.red.toUpperCase(); // ❌ エラー: string | number[] にはtoUpperCaseがない infer — 型変数の宣言的導入
inferはConditional Types内で使い、型変数を宣言的に導入します。
既存の型から一部を「抽出」するのに不可欠です。
// 関数の戻り値型を抽出(ReturnType の実装)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type A = MyReturnType<() => string>; // string
type B = MyReturnType<(x: number) => void>; // void
// 配列の要素型を抽出
type ElementType<T> = T extends (infer E)[] ? E : never;
type C = ElementType<string[]>; // string
type D = ElementType<number[]>; // number
// Promiseのアンラップ(Awaited の簡易実装)
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type E = UnwrapPromise<Promise<string>>; // string
type F = UnwrapPromise<number>; // number // 型安全なルーティング定義
type Route = { path: string; method: "GET" | "POST" | "PUT" | "DELETE" };
const routes = [
{ path: "/users", method: "GET" },
{ path: "/users", method: "POST" },
{ path: "/users/:id", method: "PUT" },
] as const satisfies readonly Route[];
// routes[0].method の型は "GET"(リテラル型) ← as const の効果
// かつ Route 型に適合していることが保証される ← satisfies の効果 理解度チェック
ジェネリクスの制約 <T extends { length: number }> の意味として正しいものはどれですか?
キーボード: 1〜4 で選択、Enter で回答