TypeScript で以下の型定義があるとき、コンパイルエラーになるコードはどれですか? type Role = 'admin' | 'editor' | 'viewer'; type RoleConfig = Record<Role, { label: string; canEdit: boolean }>; const config: RoleConfig = { ... };
解説
Record<K, V> は「K に指定したすべてのキーについて、値の型が V であるオブジェクト」を定義するユーティリティ型です。※イチから型を作り直す手間を省き、「元の型の一部だけ使う」「全部オプションにする」といった変形を簡単に行えます。今回 K にユニオン型 'admin' | 'editor' | 'viewer' を渡しているため、3つのキーすべてを漏れなく定義する義務が生じます。viewer を省略した選択肢Bはこの制約に違反するためコンパイルエラーになります。選択肢Aは全キーを網羅しているので問題ありません。選択肢Cは値の型が { label: string; canEdit: boolean } に合致しているので正常です。選択肢Dは Record によって viewer キーの存在が保証されているため、安全にアクセスできます。Record が内部でやっていることRecord<K, V> は TypeScript の組み込みユーティリティ型で、定義は次のとおりです。type Record<K extends keyof any, V> = { [P in K]: V; };これは Mapped Types(マップ型)という仕組みを使い、K の各メンバーをキーとして展開し、すべてに同じ値型 V を割り当てます。つまり今回の Record<Role, { label: string; canEdit: boolean }> は以下と同義です。type RoleConfig = { admin: { label: string; canEdit: boolean }; editor: { label: string; canEdit: boolean }; viewer: { label: string; canEdit: boolean }; };Record と Partial を組み合わせるパターン「一部のキーだけ定義したい」場合は Partial<Record<K, V>> を使います。Partial はすべてのプロパティをオプショナルにするユーティリティ型なので、viewer を省略してもエラーになりません。type OptionalConfig = Partial<Record<Role, { label: string; canEdit: boolean }>>; // viewer がなくてもOK const config: OptionalConfig = { admin: { label: '管理者', canEdit: true }, };ただし Partial を使うと各キーが undefined になり得るため、アクセス時にはオプショナルチェーン(config.viewer?.canEdit)が必要です。「全キー必須で漏れを防ぎたいなら素の Record、柔軟に使いたいなら Partial と組み合わせる」という使い分けが実務の基本方針です。Record を使うと嬉しい場面Record の最大のメリットは、ユニオン型にメンバーを追加したときにコンパイラが未定義のキーを検出してくれる点です。たとえば Role に 'owner' を追加すると、config の定義箇所で即座にエラーが出ます。設定オブジェクトや権限マップなど「列挙したパターンすべてに対応を書き漏らしたくない」場面で、switch 文の網羅性チェックと同様の安全性を型レベルで得られます。