はじめに
この投稿は、TypeScript学習者がRecordとMap、そして型推論の使い分けについて、自身の理解を深めるために書いています。
TypeScriptでキーと値のペアを扱う際、オブジェクトリテラル(型推論)やRecord型、Mapなど複数の選択肢があり、どれを選ぶべきか迷うことがありました。これらは似ているようで、型安全性や実務での扱い方に明確な違いがあります。それぞれの特性と選び方についてまとめます。
1. オブジェクトリテラル(型推論)とRecord型の違い
変数を定義する際、型注釈をつけない場合は型推論が働きます。これとRecord型では、型の厳密さが異なります。
// 推論される型は{math:number}。math以外のキーにはアクセスできない(安全)
const score = {math:100};
// console.log(score.english); // コンパイルエラーになる:Property 'english' does not exist on type '{ math: number; }'.
// Record<string,number>は「どんな文字列キーでも数値が返る」と約束する
// 存在しないキーでもエラーにならずundefinedが返る可能性がある(危険)
const scoreRecord:Record<string,number> = {math:100};
console.log(scoreRecord.english); // undefined(コンパイルエラーにならない)
2. RecordとMapの安全性比較
Record(オブジェクト)は存在しないキーにアクセスしてもコンパイルエラーになりませんが、Mapは型定義によって安全性を強制できます。
// Recordの場合:存在しないキーでもstring型と推論される
type Settings = Record<string,string>;
const config:Settings = {theme:"dark"};
const fontSize = config.fontSize; // 型はstringだが実行結果はundefined
// console.log(fontSize.toUpperCase()); // 実行時にTypeErrorでクラッシュする:Cannot read properties of undefined (reading 'toUpperCase')
// Mapの場合:存在しないキーはundefinedを返し、コンパイル時にチェックを強制する
const settingsMap = new Map<string,string>();
settingsMap.set("theme","dark");
const mapSize = settingsMap.get("fontSize"); // 型はstring|undefined
// console.log(mapSize.toUpperCase()); // コンパイルエラー:'mapSize' is possibly 'undefined'.
3. シリアライズと使い分け
RecordはJavaScriptのオブジェクトそのものであるため、JSON.stringifyで文字列化が可能です。一方、MapはそのままではJSON化できないため、用途に合わせて選ぶ必要があります。
// RecordはAPI通信やローカルストレージへの保存に向いている
const data = {id:1,name:"user"};
const json = JSON.stringify(data); // '{"id":1,"name":"user"}'
// Mapは頻繁な更新や、オブジェクトの参照をキーにしたい場合に適している
const map = new Map<object,string>();
const user = {id:1};
map.set(user,"active");
4. 補足:Recordの安全性を高める設定について
ここまでRecord(オブジェクト)の型安全性の穴について触れましたが、TypeScriptの設定(tsconfig.json)にあるnoUncheckedIndexedAccessを有効にすることで、この挙動を厳格にすることも可能です。
この設定をONにすると、Recordのプロパティにアクセスした際の型が自動的にstring | undefinedとなり、Mapと同様に事前のチェックを強制されます。
ただし、環境設定に依存することになるため、設定に関わらず構造として安全に保ちたい場合や、特定のロジック内で厳密に管理したい場合には、やはりMapを選択するメリットがあります。
まとめ
- キーが固定されている場合は、型推論やinterfaceを使い、厳密に管理する。
- RecordはAPIでのデータやり取りや、型定義を柔軟にしたい場合に使用する。
- Mapはキーが動的である場合や、コンパイル時に未定義アクセスのミスを検知したい内部ロジックに適している。
- どちらが優れているかではなく、シリアライズの必要性と型安全性のどちらを優先するかで使い分ける。