【TypeScript】mapped typesで型を自動生成 - 詳細解説

【TypeScript】mapped typesで型を自動生成 - 詳細解説

2024-11-10

2024-11-10

概要

TypeScriptのmapped typesは、既存の型を基に新しい型を自動生成する機能で、型の再利用性と保守性を向上させる強力なツールです。例えば、オプションの型、読み取り専用の型、必須の型など、プロパティに対して条件を付けた型を簡単に作成できます。本記事では、mapped typesの基本から応用例までを詳しく解説し、効率的な型定義の方法を学びます。

mapped typesとは?

mapped typesは、既存の型に対して修飾や制限を加えた新しい型を動的に生成するためのTypeScriptの機能です。{ [P in K]: T }のようなシンタックスを用いて、オブジェクト型のプロパティを一括で操作できます。例えば、あるオブジェクト型のプロパティすべてをオプションにしたり、読み取り専用にしたりといった操作が可能です。

mapped typesの基本構文

基本的なmapped typesの構文は以下の通りです。

type MappedType<T> = {
  [P in keyof T]: T[P];
};

ここで、T型の各プロパティPが新しい型MappedTypeとして定義されます。keyofは、オブジェクト型のすべてのプロパティのキーを取得するユーティリティです。

mapped typesの代表例

Partial - 部分型の生成

Partialは、すべてのプロパティをオプションにした型を生成します。例えば、オプションのプロパティが増えるフォームのデータや、APIのレスポンス型などに便利です。

type User = {
  id: number;
  name: string;
  email: string;
};
type PartialUser = Partial<User>; // すべてのプロパティがオプションになる
const user1: PartialUser = {
  name: "Alice"
}; // 問題なし

Readonly - 読み取り専用の型

Readonlyは、すべてのプロパティを読み取り専用にする型を生成します。この型は、データが誤って変更されないように保護したいときに役立ちます。

type ReadonlyUser = Readonly<User>;
const user2: ReadonlyUser = {
  id: 1,
  name: "Bob",
  email: "bob@example.com"
};
// user2.name = "Alice"; // エラー: 読み取り専用プロパティ

Required - 必須プロパティの指定

Requiredは、すべてのプロパティを必須にした型を生成します。通常オプションのプロパティを持つ型を確実にすべて設定する必要がある場合に使用します。

type UserWithOptional = {
  id: number;
  name?: string;
};
type CompleteUser = Required<UserWithOptional>;
const user3: CompleteUser = {
  id: 2,
  name: "Charlie"
}; // nameプロパティが必須になる

Record - キーの一覧に対する型を一括設定

Recordは、指定したキーの一覧に対して同じ型を一括で割り当てる型です。例えば、オブジェクトのキーに対してすべて同じ型の値を持たせたい場合に使います。

type Role = "admin" | "user" | "guest";
type Permissions = Record<Role, string[]>;
const permissions: Permissions = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"]
};

カスタムmapped typesの作成

mapped typesは、カスタムの型を柔軟に作成するために用いることもできます。たとえば、すべてのプロパティをオプションにし、同時にプロパティの型を変更するカスタム型を作成できます。

Nullable - プロパティをnull可能にする型

以下の例では、すべてのプロパティをnull許容にするNullable型を作成しています。

type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};
type NullableUser = Nullable<User>;
const user4: NullableUser = {
  id: null,
  name: "Dave",
  email: null
};

Mutable - Readonlyプロパティを変更可能に

TypeScriptReadonly型で設定したプロパティを再び変更可能にするMutable型を定義することも可能です。

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};
type MutableUser = Mutable<ReadonlyUser>;
const user5: MutableUser = {
  id: 1,
  name: "Eve",
  email: "eve@example.com"
};
user5.name = "Alice"; // 問題なし

-readonlyとすることで、プロパティの読み取り専用修飾を解除し、変更可能なプロパティに戻しています。

条件付き型と組み合わせた応用

mapped typesは条件付き型(conditional types)と組み合わせることで、さらに高度な型操作が可能です。例えば、never型を使用して特定のプロパティを除外する型などを作成できます。

PickとExcludeで特定のプロパティを取り出す

PickExcludeを使うことで、特定のプロパティのみを抽出したり除外したりすることができます。

type PickNameAndEmail = Pick<User, "name" | "email">;
const user6: PickNameAndEmail = {
  name: "Frank",
  email: "frank@example.com"
};
type ExcludeEmail = Exclude<keyof User, "email">; // "id" | "name" のみ

Pickは指定したプロパティだけを含む新しい型を生成し、Excludeは指定した型を除外したユニオン型を生成します。

DeepReadonly - ネストしたプロパティも読み取り専用にする

ネストされたオブジェクトのプロパティも含めてすべて読み取り専用にするには、再帰的なmapped typesを使います。

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
type ComplexUser = {
  id: number;
  profile: {
    name: string;
    age: number;
  };
};
type ReadonlyComplexUser = DeepReadonly<ComplexUser>;
const complexUser: ReadonlyComplexUser = {
  id: 1,
  profile: {
    name: "Grace",
    age: 29
  }
};
// complexUser.profile.name = "Alice"; // エラー

この例では、DeepReadonly型を使用してネストされたプロパティもすべて読み取り専用にしています。再帰的な条件でobject型を判定し、オブジェクトであれば再帰的にDeepReadonlyを適用しています。

まとめ

TypeScriptのmapped typesは、型の柔 軟な操作や再利用性を高めるために欠かせない機能です。既存の型に対してプロパティを動的に変更し、PartialやReadonlyといったユーティリティ型からカスタムmapped typesまで幅広く活用することができます。mapped typesと条件付き型を組み合わせて複雑な型操作を実現し、プロジェクトのメンテナンス性を向上させましょう。

Recommend