TypeScript Mapped Types 公式ドキュメント
はじめに
TypeScript の Mapped Types(マップ型) は、既存の型を基に新しい型を生成する機能です。
TypeScript 公式ドキュメントの内容に基づき、Mapped Types の基本概念から応用的な使用方法まで、コード例とともに解説します。
Mapped Types
繰り返しを避けたい場合、ある型を別の型に基づいて作成する必要があることがあります。
Mapped Types は、インデックスシグネチャの構文に基づいて構築されます。インデックスシグネチャは、事前に宣言されていないプロパティの型を宣言するために使用されます:
type OnlyBoolsAndHorses = {
[key: string]: boolean | Horse;
};
const conforms: OnlyBoolsAndHorses = {
del: true,
rodney: false,
};
Mapped Type は、PropertyKey
の union(しばしばkeyof で作成される)を使用してキーを反復処理し、型を作成するジェネリック型です:
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
この例では、OptionsFlags
は型 Type
からすべてのプロパティを取得し、その値を boolean に変更します。
type Features = {
darkMode: () => void;
newUserProfile: () => void;
};
type FeatureOptions = OptionsFlags<Features>;
// ^? type FeatureOptions = {
// darkMode: boolean;
// newUserProfile: boolean;
// }
Mapping Modifiers
マッピング中に適用できる 2 つの追加修飾子があります:readonly
と ?
で、それぞれ可変性とオプション性に影響します。
これらの修飾子を削除または追加するには、-
または +
を前に付けます。プレフィックスを追加しない場合、+
が想定されます。
// 型のプロパティから 'readonly' 属性を削除
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
// ^? type UnlockedAccount = {
// id: string;
// name: string;
// }
// 型のプロパティから 'optional' 属性を削除
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type User = Concrete<MaybeUser>;
// ^? type User = {
// id: string;
// name: string;
// age: number;
// }
as
Key Remapping via TypeScript 4.1 以降では、mapped type で as
句を使用してキーを再マッピングできます:
type MappedTypeWithNewProperties<Type> = {
[Properties in keyof Type as NewKeyType]: Type[Properties];
};
template literal types などの機能を活用して、以前のプロパティ名から新しいプロパティ名を作成できます:
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<
string & Property
>}`]: () => Type[Property];
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// ^? type LazyPerson = {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }
条件型を通じて never
を生成することでキーをフィルタリングできます:
// 'kind' プロパティを削除
type RemoveKindField<Type> = {
[Property in keyof Type as Exclude<Property, "kind">]: Type[Property];
};
interface Circle {
kind: "circle";
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
// ^? type KindlessCircle = {
// radius: number;
// }
string | number | symbol
の union だけでなく、任意の型の union をマップできます:
type EventConfig<Events extends { kind: string }> = {
[E in Events as E["kind"]]: (event: E) => void;
};
type SquareEvent = { kind: "square"; x: number; y: number };
type CircleEvent = { kind: "circle"; radius: number };
type Config = EventConfig<SquareEvent | CircleEvent>;
// ^? type Config = {
// square: (event: SquareEvent) => void;
// circle: (event: CircleEvent) => void;
// }
Further Exploration
Mapped Types は、この型操作セクションの他の機能とうまく連携します。例えば、ここでは条件型を使用した mapped typeがあり、オブジェクトが pii
プロパティをリテラル true
に設定しているかどうかに応じて true
または false
を返します:
type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};
type DBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
// ^? type ObjectsNeedingGDPRDeletion = {
// id: false;
// name: true;
// }
まとめ
TypeScript の Mapped Types を使用することで、既存の型から新しい型を効率的に生成できます。
公式ドキュメントで紹介されている主要な概念:
-
基本的なマッピング:
[K in keyof T]
を使用した型の変換 -
修飾子の操作:
readonly
と?
の追加・削除 -
キーの再マッピング:
as
句を使用した新しいプロパティ名の生成 - 条件型との組み合わせ: より柔軟な型変換の実現
重要なポイント
- Mapped Types はジェネリクスと組み合わせることで真価を発揮
- 修飾子
+
と-
により、プロパティの特性を制御可能 - Template Literal Types と組み合わせて動的なプロパティ名を生成
- 条件型と組み合わせてプロパティのフィルタリングが可能
この機能により、型レベルでの変換処理を実現し、より型安全で保守性の高い TypeScript コードを書けるようになります。
参考リンク
Discussion