🕸️

TypeScriptのMapped Typesについて知ろう

に公開

概要

別記事を書くにあたりMapped Typesを理解しようの会

Mapped Typesとは

リテラル型を反復処理してそれをプロパティ名とした新しい型を作成するもの。
参考: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html

下記のようにリテラル型Keysを反復処理して型Hogeを作成できる。

type Keys = "name" | "message";
type Hoge = { [Mapped in Keys]: string }; // リテラル型 Keys を反復処理して Hoge を作成
const hoge: Hoge = { name: "ほげ太郎", message: "こんにちは、ほげ太郎です。"};

Mapped Typesはオブジェクトのプロパティ名の定義に対して使用できる。

type Hoge = { [Mapped in Keys]: string }; // ✅プロパティ名を反復処理で指定しているのでOK
type Hoge = { string; [Mapped in Keys] }; // 🚫プロパティ値を反復処理で指定しているのでNG

ジェネリクス型を用いて以下のような表現をされることがあってややこしくなるが、基本的にfor文みたいに反復処理できるのが入ってくるくる回してるんだねぇ。と思っているとスッキリするかも。

type Hoge<T> = {
  [Mapped in keyof T]: string;
};

type Fuga<T extends string | number> = {
  [Mapped in T]: string;
};

ちなみに、先ほどプロパティ名に対してだけ使用できると書いたが、設定した変数自体はプロパティ値でも使用可能。

type Keys = "name" | "message" | "age";
type Objects = {
  name: string;
  message: string;
  age: number;
};

type Hoge<T extends Record<Keys, string | number>> = {
  [Mapped in Keys]: T[Mapped]; // ✅Mapped Typesで定義したMappedはプロパティ値でも使用可能
};

const hoge: Hoge<Objects> = {
  name: "ほげ太郎",
  message: "こんにちは!",
  age: 20,
};

Mapping Modifiers(マッピング修飾子)とは

Mapped Typesで型定義をする際に readonly? を付け外しするもの。
+- を用いる。
参考: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers

+ を用いた追加例

readonlyの追加例
type Keys = "name" | "message";
type Hoge = { [Mapped in Keys]: string };
type Fuga = { +readonly [Mapped in Keys]: string }; // +を付けてreadonlyを付与

const hoge: Hoge = { name: "ほげ太郎", message: "こんにちは、ほげ太郎です。" };
const fuga: Fuga = { name: "ふが太郎", message: "こんにちは、ふが太郎です。" };

hoge.name = "ほげ太郎2"; // ✅プロパティの変更がOK
fuga.name = "ふが太郎2"; // 🚫readonlyなのでプロパティの変更がNG
?の追加例
type Keys = "name" | "message";
type Hoge = { [Mapped in Keys]: string };
type Fuga = { [Mapped in Keys]+?: string };

const hoge: Hoge = { name: "ほげ太郎" }; // 🚫プロパティの省略はNG
const fuga: Fuga = { name: "ふが太郎" }; // ✅オプショナルが追加されたのでプロパティの省略があってもOK

とはいえ、一般的に + は省略され以下のように書かれることが多い。

// readonlyを追加する例
type Fuga = { readonly [Mapped in Keys]: string };
// ?を追加する例
type Fuga = { [Mapped in Keys]?: string };

- を用いた削除例

readonlyの削除例
type Objects = { readonly name: string; readonly message: string };

type Hoge = { [Mapped in keyof Objects]: string };
type Fuga = { -readonly [Mapped in keyof Objects]: string };

const hoge: Hoge = { name: "ほげ太郎", message: "こんにちは、ほげ太郎です。" };
const fuga: Fuga = { name: "ふが太郎", message: "こんにちは、ふが太郎です。" };

hoge.name = "ほげ太郎2"; // 🚫readonlyなのでプロパティの変更がNG
fuga.name = "ふが太郎2"; // ✅プロパティの変更がOK
?の削除例
type Objects = { name?: string; message?: string };

type Hoge = { [Mapped in keyof Objects]: string };
type Fuga = { [Mapped in keyof Objects]-?: string };

const hoge: Hoge = { name: "ほげ太郎" }; // ✅オプショナルなのでプロパティの省略があってもOK
const fuga: Fuga = { name: "ふが太郎" }; // 🚫オプショナルが削除されたのでプロパティの省略はNG

Discussion