ジェネリクス — 型のパラメータ化

ジェネリクスは、型をパラメータとして受け取り、再利用可能なコンポーネントを作る仕組みです。 「具体的な型を後から決める」ことで、型安全性を保ちながら汎用的なコードを書けます。

// ジェネリクスなし — 型安全性が失われる
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 の効果

理解度チェック

問題 0 / 50%
Q1

ジェネリクスの制約 <T extends { length: number }> の意味として正しいものはどれですか?

キーボード: 1〜4 で選択、Enter で回答