TypeScript Record型で型安全なディクショナリを作る方法

問題

APIレスポンスコード別のメッセージをオブジェクトで管理していますが、新しいコードを追加した時に漏れがあってもTypeScriptが検出してくれません。{ [key: string]: string }のようなインデックスシグネチャは緩すぎます。

解決方法

Record<K, V>を使えば、キーと値の両方を型レベルで強制できます。

type StatusCode = 'success' | 'error' | 'pending' | 'timeout';

const statusMessages: Record<StatusCode, string> = {
  success: '完了しました',
  error: 'エラーが発生しました',
  pending: '処理中です',
  timeout: 'タイムアウトしました',
};
// 一つでも漏れるとコンパイルエラー

StatusCodeに新しい値を追加すると、statusMessagesで即座にエラーが発生します。漏れることがありません。

enumキーとの組み合わせはさらに強力です。

enum Permission {
  Read = 'read',
  Write = 'write',
  Delete = 'delete',
}

const permissionLabels: Record<Permission, string> = {
  [Permission.Read]: '読み取り',
  [Permission.Write]: '書き込み',
  [Permission.Delete]: '削除',
};

すべてのキーを強制したくない場合はPartialと組み合わせます。

// 一部だけ定義してもOK
const overrides: Partial<Record<StatusCode, string>> = {
  error: 'サーバーエラーです',
};

ネストしたオブジェクトにも便利です。

type Locale = 'ko' | 'en' | 'ja';

const translations: Record<Locale, Record<string, string>> = {
  ko: { greeting: '안녕하세요' },
  en: { greeting: 'Hello' },
  ja: { greeting: 'こんにちは' },
};

ポイント

  • Record<K, V>はKのすべてのキーに対してV型の値を強制します
  • ユニオン型やenumをキーにすると、漏れたキーをコンパイル時に検出できます
  • 一部だけ必要な場合はPartial<Record<K, V>>を使います
  • { [key: string]: V }のインデックスシグネチャよりはるかに精密です
  • 設定マッピング、i18n、状態管理などでよく使われます