naoki.dev

記事検索

タイトル・本文・タグから検索します

ホームに戻る
フロントエンド

TypeScript の Record 型を使いこなす

目次

Record<K, V> は「キーが K・値が V のオブジェクト」を表すユーティリティ型です。インデックスシグネチャより簡潔に書け、キーをユニオンに絞ると網羅性チェックまで効きます。

Record とは

Record<Keys, Value> で、指定したキー集合と値の型を持つオブジェクト型を作れます。

type Scores = Record<string, number>;
 
const scores: Scores = {
  math: 80,
  english: 92,
};

これは { [key: string]: number } とほぼ同じ意味です。

インデックスシグネチャとの違い

任意の文字列キーなら両者はほぼ同等ですが、キーをユニオンに絞れるのが Record の強みです。

// インデックスシグネチャ: キーは string 全般
type A = { [key: string]: number };
 
// Record: キーを具体的なユニオンに限定できる
type B = Record<"x" | "y", number>; // { x: number; y: number }

「決まったキーだけを持つ」ことを表したいなら Record が簡潔です。

ユニオンをキーにすると網羅性が効く

キーにユニオン型を渡すと、キーの抜け漏れがコンパイルエラーになります。

type Role = "admin" | "editor" | "viewer";
 
const permissions: Record<Role, string[]> = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
};

viewer を書き忘れるとエラーになるため、ロールが増えたときの対応漏れを防げます。

satisfies と組み合わせる

「値の形は検査したいが、各値の具体型も保ちたい」場合は satisfies と併用します。

const theme = {
  primary: "#ea580c",
  bg: "#09090b",
} satisfies Record<string, string>;
 
theme.primary.toUpperCase(); // OK: primary は string と推論されたまま

型注釈 : Record<string, string> だと推論が広がりますが、satisfies なら適合検査と具体的な推論を両取りできます。

落とし穴: 存在しないキーへのアクセス

Record<string, V> は「どんなキーでも V が返る」と見なされるため、実際には存在しないキーでも型エラーになりません。

const scores: Record<string, number> = { math: 80 };
 
scores.science.toFixed(); // 型上は number。だが実行時は undefined

これを防ぐには tsconfig.jsonnoUncheckedIndexedAccess を有効にします。すると戻り値が number | undefined になり、アクセス前のチェックを強制できます。

{
  "compilerOptions": {
    "noUncheckedIndexedAccess": true
  }
}

まとめ

Record<K, V> は、決まったキーを持つオブジェクトを簡潔に型付けできるユーティリティ型です。ユニオンキーで網羅性を、satisfies で推論の保持を、noUncheckedIndexedAccess で安全性を補えます。インデックスシグネチャを書く前に、まず Record で表せないか検討してみてください。

関連記事