Featured image of post マッピング型(Mapped Types)による柔軟な既存型からの型変換 Featured image of post マッピング型(Mapped Types)による柔軟な既存型からの型変換

マッピング型(Mapped Types)による柔軟な既存型からの型変換

TypeScriptのマッピング型(Mapped Types)を使った柔軟な型変換テクニックを解説。インデックスシグネチャを活用し、既存インターフェースの全プロパティをReadonlyに変換する方法や、プロパティ名や値の型を動的に変更する高度な型操作を紹介します。

はじめに

マッピング型(Mapped Types) は、既存のオブジェクト型のキーをイテレーションして新しい型を生成する機能です。Array.prototype.map() の型レベル版といえます。{ [K in keyof T]: T[K] } という構文で、各プロパティに変換を適用します。


基本のマッピング型

最もシンプルなマッピング型は、既存の型をそのままクローンします:

type Clone<T> = { [K in keyof T]: T[K] };

interface User {
  name: string;
  age: number;
}

type UserClone = Clone<User>;
// { name: string; age: number }

修飾子の追加

マッピング型では三つの修飾子を適用できます:readonly?(オプショナル)、-(除去):

// 全プロパティを読み取り専用に
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

// 全プロパティをオプショナルに
type MyPartial<T> = { [K in keyof T]?: T[K] };

// readonlyを解除(-修飾子)
type MyMutable<T> = { -readonly [K in keyof T]: T[K] };

// オプショナルを解除
type MyRequired<T> = { [K in keyof T]-?: T[K] };

これらは Readonly<T>Partial<T>Required<T> の実装そのものです。


as句によるキーのフィルタリングとリマッピング

TypeScript 4.1で導入された as を使うと、キーのフィルタリングやリネームが可能です:

// 文字列型のプロパティだけを抽出
type StringKeys<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

interface User {
  name: string;
  age: number;
  email: string;
}

type OnlyStrings = StringKeys<User>;
// { name: string; email: string }

テンプレートリテラル型との組み合わせ

キー名にテンプレートリテラル型を適用して変換します:

// 全キーにプレフィックスを追加
type WithPrefix<T, P extends string> = {
  [K in keyof T as `${P}_${K & string}`]: T[K];
};

interface User {
  name: string;
  age: number;
}

type Prefixed = WithPrefix<User, 'user'>;
// { user_name: string; user_age: number }

Getterパターンへの変換:

type Getters<T> = {
  [K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};

type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }

Pick と Omit の実装

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type MyOmit<T, K extends keyof any> = {
  [P in keyof T as P extends K ? never : P]: T[P];
};

実践的なパターン

Deep Partial(再帰的マッピング型)

type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

Nullable 変換

type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

キーのリネーム

type RenameKeys<T, M extends Record<string, string>> = {
  [K in keyof T as K extends keyof M ? M[K] : K]: T[K];
};

まとめ

マッピング型はTypeScriptの型変換機能の中核です。基本的な修飾子からas句によるキーリマッピング、テンプレートリテラルとの組み合わせまで、柔軟な型変換を可能にします。条件付き型と組み合わせれば、真のコンパイル時メタプログラミングが実現できます。