「型安全」なデータ管理、TypeScriptのRecord型について理解する
はじめに
最近、TypeScriptのRecord型を使う機会があって、この機に公式ドキュメントや関連記事を調べて、自身が理解しやすいように実用例と合わせてメモ書きしてみました。
Record<Keys, Type>とは
TypeScript公式には以下のように書かれています。
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であるオブジェクトタイプを構築します。 このユー> ティリティを使用して、あるタイプのプロパティを別のタイプにマップできます。
https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeystype
つまり、Record型は「キーと値の型を指定できる便利な型」です。
たとえば、「出席簿」というイメージ。
出席簿は「名前 → 出席番号」のように、キー(名前) と 値(番号) のペアで管理されます。
// 名前(string)がキー、番号(number)が値
// Record<string, number>で型を定義
type Attendance = Record<string, number>;
// 出席簿のデータを作成する
const attendanceA: Attendance = {
"Taro": 1,
"Hanako": 2,
"Ken": 3,
};
// 生徒名がHanakoの出席番号をログに出す
console.log(classA["Hanako"]); // 2
- string → キーの型(名前)
- number → 値の型(番号)
Recordを使うことで、キーと値に間違った型を入れたときにTypeScriptがエラーを出してくれます。
const wrong: Attendance = {
"Taro": "first" // ❌ number型じゃないのでエラー
};
実用的な例
APIレスポンスを整形する
// ユーザーデータモデル
type User = {
id: number;
name: string;
};
// ユーザーデータを一意のIDにまとめる
type UserDictionary = Record<number, User>;
// ユーザーデータを作成
const users: UserDictionary = {
1: { id: 1, name: "Alice" },
2: { id: 2, name: "Bob" },
};
// IDが2のユーザー名をログに出す
console.log(users[2].name); // "Bob"
→ idをキーにしてユーザー情報へアクセスできる。
Enumと組み合わせる
// 通信ステータス
enum Status {
Pending = "PENDING",
Success = "SUCCESS",
Error = "ERROR",
}
// 通信ステータスと文字列を紐付ける
type StatusMessage = Record<Status, string>;
// 通信ステータスに対応したメッセージを作成
const messages: StatusMessage = {
[Status.Pending]: "処理中です...",
[Status.Success]: "成功しました!",
[Status.Error]: "エラーが発生しました。",
};
// 通信ステータスがErrorに紐付いているメッセージをログに出す
console.log(messages[Status.Error]); // "エラーが発生しました。"
→ Enumをキーにすると、そのEnumに含まれる全部の値を必ず用意しなきゃいけない、とTypeScriptがチェックしてくれる。
UIテキスト辞書(多言語対応)
// 対応言語
type Locale = "ja" | "en";
// 言語ごとのテキスト
type Dictionary = Record<Locale, string>;
// テキスト辞書を作成する
const buttonText: Dictionary = {
ja: "送信",
en: "Submit",
};
// 英語のボタン文字列を表示
console.log(buttonText["en"]); // "Submit"
→ 多言語対応のUIテキストを管理できる。
設定値の管理(Partial<T>と組み合わせ)
// 設定情報
type Config = {
apiUrl: string;
retry: number;
cache: boolean;
};
// 全プロパティをオプショナルにしたRecord
type PartialConfig = Partial<Config>;
// 環境ごとの設定をまとめる
type Configs = Record<string, PartialConfig>;
const envConfig: Configs = {
dev: { apiUrl: "http://localhost:3000", retry: 1 },
prod: { apiUrl: "https://api.example.com", retry: 3, cache: true },
};
// 本番環境のAPI URLを表示
console.log(envConfig["prod"].apiUrl); // "https://api.example.com"
→ 環境設定をRecordでまとめると管理しやすくなる。
Record<K, V>とMap<K, V>の違いとは
「Record型とMap型の違い」についてこちらの記事を参考にし、以下のように整理しました。
- Map型はJavaScriptのMapオブジェクトを型付けしたもの。
- イメージ: 「メモ帳」や「付箋」
- 何をキーにしてもよい
- 書いたり消したり自由
const m = new Map<object, string>();
const obj = { name: "Hanako" };
m.set(obj, "参加者1"); // オブジェクトをキーにできる
- Record 型はTypeScript独自のユーティリティ型。
- イメージ: マス目が決まった「出席簿・チェックリスト」
- キーは固定(string | number | symbol に限定)
- 漏れなく安全に管理したいデータ向き
// 果物の種類
type Fruit = "Apple" | "Banana";
// 果物ごとの数
const stock: Record<Fruit, number> = {
Apple: 5,
Banana: 10
};
使い分けは「目的」次第だと思います。
「柔軟にデータの種類や数を増やしたり減らしたりしたい」なら Map
「固定された種類のデータを安全に管理したい(必ず値を入れたい)」なら Record
が向いています。
まとめ
- Record<Keys, Type>は「キーと値の型をしっかり決められる」便利な型。
- APIレスポンスやEnum、UIテキスト辞書など実用場面が多い。
- 使い分けは「固定パターンならRecord」「柔軟な操作ならMap」。
あとがき
TypeScriptのユーティリティ型は、「なんとなく便利そう」くらいな理解しかしていませんでしたが、Recordに関するドキュメントを実際に調べてみて、サンプルコードを実行してみると、「型をしっかり決められることで、漏れや誤りをチェックできる」といったメリットを感じました。
また、Mapとの違いも、目的に合った「どの型を選ぶか」がわかるようにしてきました。小さな型の工夫で、バグを防いだり、ソースコードも読みやすくなると感じました。
Discussion