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

TypeScriptの「型」の深淵:InterfaceのインデックスシグネチャとRecord型の決定的な違い

こんにちは。TypeScriptのコードを書き進める中で、ふと「これ、本当に型安全なのだろうか?」と不安になったことはありませんか?

特に「オブジェクトのキーが動的に決まる場合」の扱い。多くの人が最初にぶつかる壁であり、同時にTypeScriptの型システムの厳格さを理解する重要な分岐点でもあります。

今回は、Interfaceで古くから使われている「インデックスシグネチャ」と、現代的な「Record型」の決定的な違いについて、コンパイラの裏側を覗くような視点で紐解いていきましょう。ここをクリアすれば、あなたの型定義は一段階上のレベルへ到達しますよ。

—

1. そもそも「インデックスシグネチャ」とは何か?

インデックスシグネチャは、オブジェクトが「どんなキー名であっても、特定の型の値を持つ」ことを保証する仕組みです。

interface UserScores {
// 「キーは文字列で、値は数値である」と定義
[key: string]: number;
}

const scores: UserScores = {
math: 90,
english: 85,
};

// コンパイルは通るが…?
console.log(scores.science); // undefined が返ってくる!

なぜこれが「危険」なのか?

上記の例で `scores.science` にアクセスしたとき、TypeScriptは「あ、これは `number` 型だね」と判定します。しかし、実行時には `undefined` が返ってきてしまいます。

TypeScriptは「キーさえ文字列なら値は必ずあるはずだ」と信じ込んでしまいますが、現実は甘くない。存在しないキーにアクセスしても、型システム上は「数値」として扱われてしまい、後続の計算で予期せぬバグ(`NaN`の発生など)を引き起こす原因になります。

—

2. 救世主 `Record` の登場

そこで登場するのが、TypeScript標準のユーティリティ型である `Record` です。これは「キー集合 `K` に対して、値の型 `T` を割り当てる」という非常に明快な構造をしています。

具体的な使い方の比較

// インデックスシグネチャの場合
interface RawData {
[key: string]: number;
}

// Record型の場合(キーを明示的に制限する)
type Subject = ‘math’ | ‘english’ | ‘science’;
const scores: Record = {
math: 90,
english: 85,
science: 95,
};

なぜ `Record` が安全なのか?

`Record` を使うと、コンパイラに対して「どのキーが存在し得るか」を完全に掌握させることができます。もし `science` の入力を忘れたら、コンパイラは即座にエラーを出して教えてくれます。

「なんとなく文字列なら何でもいい」という曖昧な状態(インデックスシグネチャ)から、「このキーセットが正解だ」という厳格な契約(Record)への移行。これが型安全性を飛躍的に高める鍵です。

—

3. なぜ現場ではインデックスシグネチャが嫌われるのか

先ほど触れた通り、インデックスシグネチャの最大の弱点は「値が必ず存在するという嘘をつく」点にあります。

もし、本当にキーが動的で、値が存在しない可能性があるなら、以下のように書くべきです。

interface SafeData {
[key: string]: number | undefined; // 明示的に undefined を許容する
}

const data: SafeData = { math: 100 };

if (data.science !== undefined) {
// ここで初めて「数値である」と確信できる(型ガード)
console.log(data.science + 10);
}

「インデックスシグネチャを使うなら、値の型には `| undefined` を含めるのが礼儀」。これを徹底するだけで、`undefined` に起因するランタイムエラーは劇的に減ります。

—

結論:使い分けの指針

ここまで読んでくれたあなたなら、もう迷うことはありません。

  • キーが事前に決まっている(または列挙できる)場合:

迷わず `Record` を使ってください。これが最も型安全で、IDEの補完も完璧に効きます。

  • キーが完全に予測不能な外部データ(APIのレスポンス等)の場合:

インデックスシグネチャを使いますが、必ず値の型に `| undefined` を含め、利用時には型ガード(存在チェック)を行うこと。

TypeScriptの型システムは、あなたのコードの「意図」をコンパイラに伝えるための言語です。`Record` を使うことは、「私はこのデータの構造を完全に把握している」という宣言に他なりません。

この「厳格さ」こそが、大規模開発を支えるTypeScriptの真髄です。今日から、あなたの型定義に少しだけ「厳しさ」を加えてみてください。それだけで、明日からのデバッグ時間が驚くほど減るはずですよ。

それでは、また次回の深掘りでお会いしましょう!

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