TypeScriptのMapped Typesは、既存の型から新しい型を動的に生成する強力な機能です。この記事では、Mapped Typesの基本から実践的な使用方法まで、詳しく解説していきます。
Mapped Typesとは
Mapped Typesは、既存のオブジェクト型のプロパティを変換して、新しい型を作成する機能です。これにより、型の再利用性が高まり、コードの重複を減らすことができます。
基本的な構文
type MappedType<T> = {
[P in keyof T]: T[P]
}実践的な使用例
1. すべてのプロパティをオプショナルにする
type Optional<T> = {
[P in keyof T]?: T[P]
}
interface User {
name: string;
age: number;
}
type OptionalUser = Optional<User>;
// 結果: { name?: string; age?: number; }2. すべてのプロパティを読み取り専用にする
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
interface Todo {
title: string;
completed: boolean;
}
type ReadonlyTodo = Readonly<Todo>;
// 結果: { readonly title: string; readonly completed: boolean; }3. プロパティの型を変換する
type ToString<T> = {
[P in keyof T]: string
}
interface Person {
id: number;
age: number;
active: boolean;
}
type StringPerson = ToString<Person>;
// 結果: { id: string; age: string; active: string; }高度な使用例
1. 条件付きMapped Types
type ConditionalType<T> = {
[P in keyof T]: T[P] extends string ? string : number
}
interface Mixed {
name: string;
count: number;
id: string;
}
type Converted = ConditionalType<Mixed>;
// 結果: { name: string; count: number; id: string; }2. Mapped Typesとジェネリックの組み合わせ
type Record<K extends keyof any, T> = {
[P in K]: T
}
type Fruit = Record<'apple' | 'banana' | 'orange', number>;
// 結果: { apple: number; banana: number; orange: number; }ベストプラクティス
1. 型の命名: 明確で説明的な名前を使用する
2. コメント: 複雑なMapped Typesには適切なコメントを付ける
3. 再利用: 共通のパターンは独立した型として定義する
よくある間違いと解決策
1. キーの制約を忘れる
// 悪い例
type Unconstrained<T> = {
[P in T]: string // エラー: Tは制約されていない
}
// 良い例
type Constrained<T extends string | number | symbol> = {
[P in T]: string
}2. ネストされたプロパティの処理
// 再帰的なMapped Types
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}まとめ
Mapped Typesは、TypeScriptの型システムを最大限に活用するための重要な機能です。適切に使用することで、型安全性を保ちながら、柔軟で再利用可能な型定義を作成できます。
実際のプロジェクトでは、以下の点に注意して使用しましょう:
- 型の可読性を維持する
- 必要以上に複雑な型を作成しない
- ドキュメントとコメントを適切に追加する
これらの原則に従うことで、メンテナンス性の高い型定義を実現できます。