【実務・中級編】Interfaceの「インデックスシグネチャ」と「Record」の型安全性の違い – TypeScript コア・型システムの基礎解析バイブル

TypeScriptの型安全性を崩壊させる「インデックスシグネチャ」の罠と、Record型による防衛術

フロントエンドの現場で、APIレスポンスの型定義に悩んだことはないか?
「とりあえず `[key: string]: any` や `[key: string]: string` で型を通しておけばいいか」――そう考えた瞬間、君たちのプロダクトの堅牢性は砂上の楼閣と化す。

今日は、TypeScriptの型システムの中でも特に「魔境」となりやすいインデックスシグネチャと、それを代替する`Record`の挙動の深淵を紐解こう。なぜインデックスシグネチャが危険なのか、どうすれば「コンパイル時にバグを撲滅する」設計ができるのかを伝授する。

—

1. インデックスシグネチャの「甘美な嘘」

まずは、多くのエンジニアがやりがちなこの定義を見てほしい。

interface UserConfig {
[key: string]: string; // ここが諸悪の根源
theme: ‘light’ | ‘dark’; // コンパイルエラー:’light’ | ‘dark’ は string に割り当て可能か?
}

このコードはコンパイルエラーになる。「インデックスシグネチャは、他のプロパティの型を包含しなければならない」という制約だ。これに従って無理やり `string` に合わせると、今度は「存在しないキーにアクセスしても `undefined` が返らない」という、TypeScriptの型安全性を根底から覆す挙動が発生する。

const config: UserConfig = { theme: ‘light’ };
// TypeScriptは「stringが返る」と嘘をつく
const val = config.unknownKey;

// 実行時:undefined が返るが、コンパイラは string 型と判断している
console.log(val.toUpperCase()); // 実行時エラー: Cannot read property ‘toUpperCase’ of undefined

結論:インデックスシグネチャは「キーが不明で、かつ値が常に存在することが保証されている」という極めて特殊な状況以外では、決して使うべきではない。

—

2. `Record` がもたらす真の型安全性

対して、`Record` は何が違うのか。
`Record` は内部的に `Mapped Types` を利用しており、キーの存在可能性を明確に制御できる。

堅牢な設計:`Partial` の活用

APIからのレスポンスや、動的な設定オブジェクトを扱う場合、キーの存在確認を必須にするのがプロの流儀だ。

// 厳格な設定値の管理
type Theme = ‘light’ | ‘dark’;

// Record型でキーを明示的に制限する
type UserSettings = Record;

const settings: Partial = {
header: ‘light’,
};

// 安全なアクセス
const headerTheme = settings.header; // Theme | undefined
if (headerTheme) {
console.log(headerTheme.toUpperCase()); // 安全!
}

`Record` を使う最大の利点は、「キーが未定義である可能性」をコンパイラに正しく認識させることにある。これにより、開発者は必ず `undefined` チェックを強制される。これが堅牢なコードへの第一歩だ。

—

3. 実務で差がつく:ユニオン型と組み合わせた最強のパターン

インデックスシグネチャを使いたくなるシーンの多くは、実は「キーが予め決まっている」ことが多い。その場合は、`Record` ではなく `Partial>` を使うのが最も美しい。

// プロダクションで使える堅牢な設計パターン
type FieldName = ‘username’ | ‘email’ | ‘age’;

// フォームのバリデーション状態などを管理する
type FormErrors = Partial>;

const errors: FormErrors = {
username: [‘必須項目です’, ‘3文字以上で入力してください’],
};

// 存在しないキーへのアクセスも型安全
const emailError = errors.email; // string[] | undefined

この設計なら、もし `FieldName` に新しい項目が追加されても、コンパイラが「処理が漏れている箇所」を即座に指摘してくれる。インデックスシグネチャではこうはいかない。

—

チーフアーキテクトからの提言

コードレビューにおいて、私は以下の基準でインデックスシグネチャを検閲している。

1. `[key: string]: any` は禁止: それはTypeScriptを型のないJavaScriptに退化させる行為だ。
2. キーを特定できないか?: `Record` を使ってキーをユニオン型(`’a’ | ‘b’`)で定義できないか徹底的に議論すること。
3. `undefined` を許容するか?: `Partial` を組み合わせて、呼び出し側に「値がないケース」のハンドリングを強制しているか。

TypeScriptは、君たちのコードの「曖昧さ」を徹底的に排除するためのコンパイラだ。その強力な武器を、インデックスシグネチャという名の怠慢で捨てるな。

今日のコードから、`[key: string]` を `Record` に置き換えてみてほしい。それだけで、君たちのアプリケーションの実行時エラーは劇的に減るはずだ。

型を制する者は、プロダクトの寿命を制する。 健闘を祈る。

タイトルとURLをコピーしました