Typescript(マップ型)

はじめに

今回の記事はin演算子を用いたマップ型(mapped type)についての記事になります。
in演算子を用いた構文は複数あるため、ここでまとめておきます。

①オブジェクトにプロパティが存在するかどうかをチェックするin
const obj = { a: 1, b: 2, c: 3 };
console.log('a' in obj); // true

このinを型ガードに利用した記事を書きました。そちらも参考にして下さい!
Typescript(型ガード) - 駆け足エンジニアの記録

②for...in 文
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  console.log(key); //a b c
}

上記のコードのように繰り返し構文(for)を用いて、オブジェクトのプロパティ名を取得できます。

マップ型の基本

TypeScriptの構文においてトークンや式が型として評価される場所(型コンテキスト)では、in演算子が列挙された型の中から各要素の型の値を抜き出すための用途に使われます。
コードで確認しましょう。

type Fig = "one" | "two" | "three";
type FigMap = { [k in Fig]: number };
const figMap: FigMap = {
  one: 1,
  two: 2,
  three: 3,
};

エイリアス内、オブジェクトの型を定義しています。そのオブジェクトのプロパティにマップ型を利用しています。ここではFigという文字列リテラル型の中からone,two,threeの値をFigMapを型指定したオブジェクトのプロパティ名として使うよう強制させます。

またここではone,two,three全てをプロパティとして使わないとエラーが出ますが、その一部(oneとtwoだけなど)をプロパティとして使いたい場合はオプションである?を使います。

type Fig = "one" | "two" | "three";
type FigMap = { [k in Fig]?: number };
const figMap: FigMap = {
  one: 1,
  two: 2,
};

因みに、マップ型を以下のように書き換えることも可能です。

type Fig = "one" | "two" | "three";
type FigMap = Record<Fig, number>;
const figMap: FigMap = {
  one: 1,
  two: 2,
  three: 3,
};

マップ型を用いていた{ [k in Fig]: number }をRecordと置き換えました。このRecordとはレコード型でRecord<型コンテキスト、値の型>をユーティリティ型といいます。このユーティリティ型というのはTypescriptが言語レベルでいくつも提供してくれてるものでジェネリクスなどで複雑な型をRecordのように簡単に使用可能にしてくれるありがたい機能です。

ユーティリティ型の一覧はlib.es5.d.tsファイルで確認できます。このファイルの開き方はvscode内、Command+Shift+F(全ファイルにまたがってキーワード検索)の検索フォームでlibと打てば、その他のファイル>>lib.es5.d.tsが出てきます。

レコード型を確認したければlib.es5.d.tsファイル内でCommand+F(ファイル内キーワード検索)の検索フォームでRecordと打てば出てきます。

マップ型の応用

マップ型をkyeof演算子、ルックアップ型と組み合わせると、どの値の型がどのプロパティ名に対応するかを制約できます
keyof演算子、ルックアップ型については以前記事にしましたので是非参考にして下さい。
Typescript(keyof演算子) - 駆け足エンジニアの記録
Typescript(ルックアップ型) - 駆け足エンジニアの記録

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

type PieceOfPerson = {
  [K in keyof Person]?: Person[K];
};

const obj: PieceOfPerson = {
  name: "max", //ageは省略可
};

keyof Personで"name" | "age"という文字列リテラル型を取得できます。in演算子を用いて、この文字列リテラルの中からプロパティ名を指定できます。