はじめに
こんにちは ken です。最近は TypeScript を勉強しています。
TypeScript には Map
型と Record
型がありますよね。
私はこれまで Go と Python しか触れてこなかったため、key と value の組のセットといえば Map
というイメージでした。
ただ今回新たな概念として Record
型というものが登場したので自身の勉強も兼ねて記事にまとめてみました。
正確性には気を付けて書いていますが、初学者による記事のため誤り等あるかもしれません。
その場合はコメントでご指摘ください
Map vs Record
Map 型とは
Map
型は、JavaScript の Map
オブジェクトを型としたもののようです。
MDN によるMapの説明を引用します。
Map オブジェクトはキーと値のペアを保持し、キーが最初に挿入された順序を覚えています。 キーや値には任意の値(オブジェクトとプリミティブ値)を使用することができます。
なるほど。今まで慣れ親しんできた "Map" という感じがします。
MDN の説明を読むと Map
には以下の特徴があるとわかります。
- キーとして任意の型(関数、オブジェクト、あらゆるプリミティブ型)を使用可能
- プロトタイプメソッド(例:
set
,get
,has
など)を持つ - イテラブル(
for...of
など)
Record 型とは
Record
型は TypeScript 独自のユーティリティ型のようです。
Microsoft による TypeScript の公式ドキュメントには次の記載があります。
https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type
Constructs an object type whose property keys are Keys and whose property values are Type. This utility can be used to map the properties of a type to another type.
(筆者訳:キーが Keys で、バリューが Type であるオブジェクト型を構築します。このユーティリティは、ある型のプロパティを別の型にマッピングするために使用できます。)
こちらも Key と Value の組ではあるものの、その正体はオブジェクト型のようです。
実装を見てみると次のように定義されていました。
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Mapped Types
を使った素直な実装ですね。
Record
型がオブジェクトであるということから以下のような特徴があるとわかります。
- キーとして使用できる型は
string
、number
、symbol
のみ - 通常のオブジェクトとして扱われるため、オブジェクトのメソッドが使用可能
両者の特徴比較
ここまででわかった Map
型と Record
型の違いを表でまとめてみました。
特徴 | Map | Record |
---|---|---|
キーの型 | 任意(オブジェクト含む) |
string ,number , symbol のみ |
メソッド | Map 固有のメソッド(set , get , has 等) |
通常のオブジェクトメソッド |
イテレーション |
for...of , forEach 等が使用可能 |
Object.entries 等が必要 |
構造の柔軟性 | 動的(キーや値を自由に追加・削除可能) | 静的(固定されたキー構造) |
使い分け
Map
とRecord
の違いについてはこれでわかりましたが、KeyとValueの組を保持したい場合はどちらを使えばよいのかを考えてみました。
Map が適している場合:
キーとしてオブジェクトを使用する必要がある場合
Record
型はオブジェクトを Key にはとれないので、オブジェクトを Key に使いたい場合は Map
を使わざるを得ません。
// Map型の場合:✅ objectをKeyにとれる
const objectRelations = new Map<object, string>();
const obj1 = { id: 1 };
objectRelations.set(obj1, "hoge");
// Record型の場合:❌ objectをKeyにとれない
type objectRelationsByRecord = Record<object, string>; // Type 'object' does not satisfy the constraint 'string | number | symbol'
Record が適している場合:
列挙型やユニオン型とのマッピングが必要な場合
特定の文字列リテラル型やユニオン型をキーとして、それに対応する値の型を定義する場合に便利です。これにより、すべてのケースに対する値の定義が強制されます。一方、Map
型の場合は実行時まで各キーに対する値の設定が任意となり、必要なキーの実装漏れを防ぐことができません。
type Status = "pending" | "success" | "error";
type StatusMessage = Record<Status, string>;
// Record型の場合:コンパイル時に全てのキーの実装が強制される
const messages: StatusMessage = {
pending: "処理中です",
success: "成功しました",
error: "エラーが発生しました",
}; // ✅ OK
// Map型の場合:コンパイル時のチェックが緩い
const messagesMap = new Map<Status, string>();
messagesMap.set("pending", "処理中です");
messagesMap.set("success", "成功しました");
// error のケースが抜けていてもコンパイルエラーにならない ⚠️
最後に
今回は Map
型と Record
型について勉強を兼ねてまとめてみました。
確かに似てはいましたが、それぞれに特性があり全くの別物ということがわかってよかったです。
間違いなどありましたらコメントにてご指摘ください、ここまで読んでいただきありがとうございました。
参考