Mapped Types(マップ型)の概要と利用方法まとめ

JavaScript

Mapped Types(マップ型)について

Mapped Types(マップ型)とはある型をもとに新たな型を定義する際に利用される型のことをいいます。

Mapped Typesは{ [P in K]: T }のように記述します。
{ [P in K]: T }は『オブジェクトのプロパティPは型Kを満たす、プロパティの値は型T』を表現します。

すでに定義されているUnion型やオブジェクトのキーを再利用(マップ)して新しい型を定義するため、Mapped Typesという名前がつけられています。

インデックスシグネチャで定義されたオブジェクトでは任意のプロパティが設定可能でした。
しかしMapped Typesを利用することにより、オブジェクトに設定できるプロパティを指定できます。

インデックスシグネチャの詳細解説は【TypeScript】インデックスシグネチャ(Index Signatures)の概要と利用方法で紹介しています。

Mapped Typesによるプロパティの指定方法

Mapped Typesの具体的な定義方法について紹介します。

Union型を利用する方法

Union型を利用してプロパティを指定する場合は[P in Union型]: 値の型と記述します。
以下は『firstNamelastNameプロパティを持ち、値がString型のオブジェクト』をMapped Typesを利用して定義した例です。

type userObjType = {
  [key in "firstName" | "lastName"]: string;
};
// 以下のように型推論される
// type userObjType = {
//   firstName: string;
//   lastName: string;
// };

上記は以下のようにも書き換えられます。

type userAttributes = "firstName" | "lastName";

type userObjType = {
  [key in userAttributes]: string;
};

上記のMapped Typesを実際に利用した例は以下の通りです。
プロパティの過不足があったり、プロパティや値の型が違うとエラーになります。

type userAttributes = "firstName" | "lastName";

type userObjType = {
  [key in userAttributes]: string;
};
// type userObjType = {
//   firstName: string;
//   lastName: string;
// }

// OK
const taro: userObjType = {
  firstName: "Taro",
  lastName: "Suzuki",
};

// NG
// 対象外のプロパティが利用されている
const jiro: userObjType = {
  firstName: "Taro",
  lastName: "Suzuki",
  phone: "090-1234-5678",
};
// assignable to type 'userObjType'.
// Object literal may only specify known properties, and 'phone' does not exist in type 'userObjType'.

// NG
// 値の型が違う
const saburo: userObjType = {
  firstName: "Saburo",
  lastName: 123,
};
// Type 'number' is not assignable to type 'string'.(2322)


// NG
// プロパティが足りない
const shiro: userObjType = {
  firstName: "Shiro"
};
// Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'userObjType'.(2741)

keyofを利用する方法

keyofはオブジェクトのキー(プロパティ)を取り出す演算子です。keyofによって抽出されたプロパティはUnion型として定義されます。

keyof演算子を利用してプロパティを指定する場合は[P in keyof オブジェクト]: 値の型と記述します。

以下は『Person型のプロパティ(firstNamelastName)をプロパティに持ち、値がString型のオブジェクト』をMapped Typesを利用して定義した例です。

type Person = {
  firstName: string;
  lasttName: string;
};

type PersonType = keyof Person;
// "firstName" | "lastName"

type userObjType = {
  [key in PersonType]: string;
  // 以下のように直接keyofを利用してもOK
  //[key in keyof Person]: string;
};
// 以下のように型推論される
// type userObjType = {
//     firstName: string;
//     lasttName: string;
// }

Mapped Typesで指定したプロパティの設定を任意にする方法

Mapped Typesと?を組み合わせることでプロパティの設定を任意にできます。具体例は以下の通りです。

type userAttributes = "firstName" | "lastName";

type userObjType = {
  [key in userAttributes]?: string;
};
// 以下のように型推論される
// type userObjType = {
//     firstName?: string | undefined;
//     lastName?: string | undefined;
// }

上記のMapped Typesを実際に利用した例は以下の通りです。

type userAttributes = "firstName" | "lastName";

type userObjType = {
  [key in userAttributes]?: string;
};
// type userObjType = {
//   firstName: string;
//   lastName: string;
// }

// OK
const taro: userObjType = {
  firstName: "Taro",
  lastName: "Suzuki",
};

// NG
// 対象外のプロパティが利用されている
const jiro: userObjType = {
  firstName: "Taro",
  lastName: "Suzuki",
  phone: "090-1234-5678",
};
// assignable to type 'userObjType'.
// Object literal may only specify known properties, and 'phone' does not exist in type 'userObjType'.

// NG
// 値の型が違う
const saburo: userObjType = {
  firstName: "Saburo",
  lastName: 123,
};
// Type 'number' is not assignable to type 'string'.(2322)

// OK(?がない場合はNGになる)
const shiro: userObjType = {
  firstName: "Shiro"
};

参考: Utility Types(ユーティリティ型)を活用する

Utility Typesとは型変換する際に利用される型です。Utility Typesを活用することで型変換の記述が簡略化できます。Utility Typesには用途に応じた様々な型が存在します。

Utility TypesはTypeScriptのパッケージで定義されているのですぐに利用可能です。

Utility Typesの型定義にはMapped Typesが利用されているため、Utility Typesを活用することでMapped Typesを直接記述する必要がなくなります。

以下ではUtility Typesの具体的な活用例を紹介します。

プロパティが必須のオブジェクトの型を定義する場合

Required<T>を利用します。
Required<T>の型定義はtype Required<T> = { [P in keyof T]-?: T[P]; }です。-?は型定義から?を取り除く演算子です。

つまり[P in keyof T]-?: T[P];は『型Tのキーで構成されたプロパティP。プロパティPの値の型は、型Tのプロパティの値の型』を表現します。

Required<T>を利用した具体例は以下の通りです。

type Person = {
  firstName: string;
  lastName: string;
  age: number;
};

// ここでいうPerson型がRequire<T>の型Tになる
// ここでいうkeyof Person、つまり firstName | lastName | age が P になる
const taro: Required<Person> = {
  firstName: "Taro", // T[P] = Person[firstName]の型 = string型
  lastName: "Suzuki", // T[P] = Person[lastName]の型 = string型
  age: 20, // T[P] = Person[age]の型 = number型
};

// NG
// 値の型が違う
const saburo: Required<Person> = {
  firstName: "Saburo",
  lastName: 123,
  age: 20, // T[P] = Person[age]の型 = number型
};
// Type 'number' is not assignable to type 'string'.(2322)

// NG
// プロパティが不足している
const shiro: Required<Person> = {
  firstName: "Shiro"
};
// Type '{ firstName: string; }' is missing the following properties from type 'Required<Person>': lastName, age

Required<T>を利用せずMapped Typesで上記の型を定義すると以下のようになります。

type Person = {
  firstName: string;
  lastName: string;
  age: number;
};

type RequiredPerson = {
  [P in keyof Person]-?: Person[P];
};
// 以下のように型推論される
// type RequiredPerson = {
//     firstName: string;
//     lastName: string;
//     age: number;
// }

const taro: RequiredPerson = {
  firstName: "Taro",
  lastName: "Suzuki",
  age: 20,
};

プロパティが任意のオブジェクトの型を定義する場合

Partial<T>を利用します。
Partial<T>の型定義はtype Partial<T> = { [P in keyof T]?: T[P] | undefined; }です。

[P in keyof T]?: T[P] | undefined;は『任意の型Tのキーで構成されたプロパティP。プロパティPの値の型は、型Tのプロパティの値の型』を表現します。

Partial<T>を利用した具体例は以下の通りです。

type Person = {
  firstName: string;
  lastName: string;
  age: number;
};

// OK
const taro: Partial<Person> = {
  firstName: "Taro",
  lastName: "Suzuki",
  age: 20,
};

// OK
const shiro: Partial<Person> = {
  firstName: "Shiro"
};

Partial<T>を利用せずMapped Typesで上記の型を定義すると以下のようになります。

type Person = {
  firstName: string;
  lastName: string;
  age: number;
};

type PartialPerson = {
  [P in keyof Person]?: Person[P];
};
// 以下のように型推論される
// type PartialPerson = {
//     firstName?: string | undefined;
//     lastName?: string | undefined;
//     age?: number | undefined;
// }

const taro: PartialPerson = {
  firstName: "Taro",
  lastName: "Suzuki",
  age: 20,
};

まとめ

  • Mapped Typesはある型をもとに新たな型を定義する際に利用される型
  • Mapped TypesではUnion型やkeyof演算子が利用される
  • Utility Typesを活用することでMapped Typesが簡潔に記述できる

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考