TypeScript 速習チュートリアル

TypeScript マップ型

TypeScript の Mapped Types(マップ型) を使用すると、既存の型のプロパティを変換して、新しい型を作成することができます。

  • Mapped Types = 型のすべてのプロパティを一括で変換する機能
  • 代表的なもの: Partial, Readonly, Pick, Omit, Record

コード例

// シンプルな例
type Person = { name: string; age: number };

// すべてのプロパティをオプショナル(?)にする
type PartialPerson = { [P in keyof Person]?: Person[P] };

// すべてのプロパティを読み取り専用(readonly)にする
type ReadonlyPerson = { readonly [P in keyof Person]: Person[P] };

1. Mapped Types の基本構文

Mapped Types のコアとなる構文は { [P in K]: T } です。

  • P: 反復(イテレート)されている現在のプロパティ名
  • K: 反復処理の対象となるプロパティ名のユニオン型
  • T: 各プロパティの結果となる型

1.1 基本的な使用例

コード例

// オブジェクト型の定義
interface Person {
  name: string;
  age: number;
  email: string;
}

// すべてのプロパティをオプショナルにするマップ型を作成
type PartialPerson = {
  [P in keyof Person]?: Person[P];
};

// 使用例
const partialPerson: PartialPerson = {
  name: "John"
  // age と email はオプショナルなので省略可能
};

// すべてのプロパティを読み取り専用にするマップ型を作成
type ReadonlyPerson = {
  readonly [P in keyof Person]: Person[P];
};

// 使用例
const readonlyPerson: ReadonlyPerson = {
  name: "Alice",
  age: 30,
  email: "[email protected]"
};

// readonlyPerson.age = 31; // エラー: 読み取り専用プロパティのため 'age' に代入できません

2. 組み込みの Mapped Types

TypeScript の標準ライブラリには、便利な組み込み Mapped Types(ユーティリティ型)が多数含まれています。

  • Partial<T>: すべてのプロパティをオプショナルにする
  • Readonly<T>: すべてのプロパティを読み取り専用にする
  • Pick<T, K>: 指定したキーのサブセットのみを選択する
  • Omit<T, K>: 指定したキーを削除する
  • Record<K, V>: 特定のキー集合に対して値の型をマッピングする

2.1 標準ユーティリティの使用例

コード例

interface User {
  id: number;
  name: string;
  email: string;
  isAdmin: boolean;
}

// Partial<T> - すべてのプロパティをオプショナルにする
type PartialUser = Partial<User>;
// 同等: { id?: number; name?: string; email?: string; isAdmin?: boolean; }

// Required<T> - すべてのプロパティを必須にする
type RequiredUser = Required<Partial<User>>;
// 同等: { id: number; name: string; email: string; isAdmin: boolean; }

// Readonly<T> - すべてのプロパティを読み取り専用にする
type ReadonlyUser = Readonly<User>;
// 同等: { readonly id: number; readonly name: string; ... }

// Pick<T, K> - T から特定のプロパティのみを抽出して型を作成
type UserCredentials = Pick<User, "email" | "id">;
// 同等: { email: string; id: number; }

// Omit<T, K> - T から特定のプロパティを除外して型を作成
type PublicUser = Omit<User, "id" | "isAdmin">;
// 同等: { name: string; email: string; }

// Record<K, T> - キーの集合と値の型を指定して型を作成
type UserRoles = Record<"admin" | "user" | "guest", string>;
// 同等: { admin: string; user: string; guest: string; }

3. カスタム Mapped Types の作成

特定の要件に合わせて、独自のマップ型を作成し、型を自由に変換できます。

3.1 カスタムマッパーの基本

コード例

// ベースとなるインターフェース
interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

// すべてのプロパティを string 型に変換するマップ型
type StringifyProperties<T> = {
  [P in keyof T]: string;
};

// 使用例
type StringProduct = StringifyProperties<Product>;
// 同等: { id: string; name: string; price: string; inStock: string; }

// 各プロパティに対してバリデーション関数を追加するマップ型
type Validator<T> = {
  [P in keyof T]: (value: T[P]) => boolean;
};

// 使用例
const productValidator: Validator<Product> = {
  id: (id) => id > 0,
  name: (name) => name.length > 0,
  price: (price) => price >= 0,
  inStock: (inStock) => typeof inStock === "boolean"
};

4. プロパティ修飾子の操作

Mapped Types では、readonly?(オプショナル)といった修飾子を追加したり削除したりすることができます。これには +- 接頭辞を使用します。

4.1 修飾子の追加と削除

コード例

// 読み取り専用やオプショナルプロパティを含むインターフェース
interface Configuration {
  readonly apiKey: string;
  readonly apiUrl: string;
  timeout?: number;
  retries?: number;
}

// すべてのプロパティから readonly 修飾子を削除する
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

// 使用例
type MutableConfig = Mutable<Configuration>;
// 同等: { apiKey: string; apiUrl: string; timeout?: number; retries?: number; }

// すべてのオプショナルプロパティを必須にする
type RequiredProps<T> = {
  [P in keyof T]-?: T[P];
};

// 使用例
type RequiredConfig = RequiredProps<Configuration>;
// 同等: { readonly apiKey: string; readonly apiUrl: string; timeout: number; retries: number; }

5. 高度な Mapped Types

Mapped Types は、Conditional Types(条件付き型)と組み合わせることでさらに強力になります。

5.1 Conditional Types との組み合わせ

コード例

// ベースとなるインターフェース
interface ApiResponse {
  data: unknown;
  status: number;
  message: string;
  timestamp: number;
}

// 条件付きマップ型: 数値型のプロパティのみをフォーマット済み文字列に変換
type FormattedResponse<T> = {
  [P in keyof T]: T[P] extends number ? string : T[P];
};

// 使用例
type FormattedApiResponse = FormattedResponse<ApiResponse>;
// 同等: { data: unknown; status: string; message: string; timestamp: string; }

// 別の例: string 型のプロパティのみを抽出してフィルタリング
type StringPropsOnly<T> = {
  [P in keyof T as T[P] extends string ? P : never]: T[P];
};

// 使用例
type ApiResponseStringProps = StringPropsOnly<ApiResponse>;
// 同等: { message: string; }

6. まとめ

Mapped Types を使用すると、一貫性のある方法ですべてのプロパティを変換できます。

6.1 重要なポイント

  • 型の変換: プロパティの型を一括で変更可能
  • プロパティ修飾子: readonly? を自在に追加・削除可能
  • キーの再マッピング: as 句を使用して、キー名の変更やフィルタリングが可能
  • コンポジション: 他の TypeScript 機能(Conditional Types など)と組み合わせて高度なロジックを構築可能

6.2 主なユースケース

  • 型の読み取り専用バージョンの作成
  • すべてのプロパティを必須、またはオプショナルに変更
  • プロパティ型の変換(例:すべてを null 許容にする、または読み取り専用にする)
  • 型に基づいたプロパティのフィルタリング
  • 型安全なユーティリティ関数の作成