こんにちは!TypeScriptを学び始めると、動的にキーが変わるオブジェクトを扱いたくなる場面が必ずやってきますよね。
例えば、「ユーザーIDをキーにして、それぞれのユーザー情報を保持するオブジェクトを作りたい!」というときです。そんな時によく登場するのが、今回テーマにする「インデックスシグネチャ」と、ユーティリティ型である`Record
「どっちを使っても同じように動く気がするけれど、何が違うんだろう?」
「エラーが出て困ったとき、どう対処すればいいんだろう?」
そんな疑問を持っていませんか?
ここをクリアすれば、TypeScriptの型システムの本質がグッと見えてきて、コードの安全性も跳ね上がりますよ。一緒に優しく紐解いていきましょう!
—
1. 動的なオブジェクトを表現する2つのアプローチ
まずは、それぞれの基本的な書き方と、コードが意味するところを確認してみましょう。
インデックスシグネチャとは?
インデックスシグネチャは、オブジェクトが「どんな名前(キー)でも持てるけれど、値の型はこれに統一してね」とコンパイラに伝えるための構文です。
// インデックスシグネチャの基本
interface UserDatabase {
// キーは文字列、値は数値(ユーザーの年齢)であることを保証
[key: string]: number;
}
const ages: UserDatabase = {
Alice: 25,
Bob: 30,
Charlie: 22,
};
ここで `[key: string]: number;` という部分がインデックスシグネチャです。「プロパティ名は何でもいい(string)、でも中身は必ず数字(number)にしてね」というルールをオブジェクトに課しています。
`Record` とは?
一方の `Record
// Record
// キーは文字列、値は数値
const agesByRecord: Record
Alice: 25,
Bob: 30,
Charlie: 22,
};
「あれ? やっていることは全く一緒じゃないか!」と思いましたよね。その通り、パッと見の振る舞いはほぼ同じです。しかし、型システムの内部や、より厳密な設計をするときに決定的な違いが生まれます。
—
2. どこが違うの? 型安全性の観点から比較する
ここからが少し面白いところです。先輩エンジニアとして、実務で差が出るポイントをいくつかお伝えしますね。
① キーの制約(柔軟性と厳格さ)
インデックスシグネチャのキーは、基本的に `string` か `number` しか使えません。
これに対して、`Record
// 役割の型定義
type Role = ‘admin’ | ‘editor’ | ‘viewer’;
// OK: Recordなら、キーを特定の文字列だけに制限できる!
const permissions: Record
admin: true,
editor: true,
viewer: false,
// 試しに “guest” などを入れると、TypeScriptが「足りないよ!」と怒ってくれます
};
もしこれをインデックスシグネチャでやろうとすると、キーが `string` 全体に広がってしまうため、「定義し忘れたキーがあってもコンパイルエラーにならない」という穴が生まれてしまいます。
② 他のプロパティとの同居(混在のしやすさ)
「動的なキーを持ちつつ、決まった名前のプロパティも一緒に持たせたい」という要件はよくあります。
例えば、ユーザーデータベースに `version` という固定のプロパティを持たせたい場合です。
// インデックスシグネチャの場合:自然に混在できる
interface FlexibleDatabase {
version: string; // 固定のプロパティ
[key: string]: string; // 動的なプロパティ(値の型は共通化する必要がある点に注意)
}
const db: FlexibleDatabase = {
version: ‘1.0.0’,
user1: ‘Alice’,
user2: ‘Bob’,
};
インデックスシグネチャは、このように「定型のプロパティ」と「動的なプロパティ」を同じインターフェース内で同居させるのが得意です。
—
3. 陥りやすい文法エラーと落とし穴
ここで、初心者がよくハマりがちな「TypeScriptの優しさ(厳しさ)」を覗いてみましょう。
落とし穴:存在しないプロパティにアクセスしたときの挙動
インデックスシグネチャや `Record
const scores: Record
Math: 90,
};
// 存在しないキー “English” にアクセス
// コンパイルエラーにはならず、実行時には `undefined` になる
const englishScore = scores.English;
もし、コードの安全性を高めるために `noUncheckedIndexedAccess` というTypeScriptのコンパイラオプションを有効にしていると、ここでの戻り値の型が自動的に `number | undefined` になり、「本当に値が存在するかチェックしてね!」と教えてくれるようになります。動的オブジェクトを扱うときは、この挙動の違いを頭の片隅に置いておくとバグを防げますよ。
—
4. どっちを使うべき? 迷ったときの判断基準
ここまで見てきた特徴を整理して、現場でどう使い分けるべきかの基準をまとめますね。
| 比較項目 | インデックスシグネチャ (`[key: string]: T`) | `Record
| :— | :— | :— |
| 主な用途 | 既存のインターフェース拡張や、固定プロパティとの混在 | 独立したマップ構造、キーを厳密に制限したいとき |
| キーの柔軟性 | `string` または `number` のみ | Union型(`’a’ \| ‘b’`)など柔軟に指定可能 |
| 可読性 | 伝統的なオブジェクト指向的な見た目 | 関数的・ユーティリティ的なスッキリした見た目 |
先輩からのアドバイス
- キーを特定の文字列リテラルやUnion型に制限したい場合 ➔迷わず `Record
` を選びましょう。型安全性がグッと上がります。 - 既存の `interface` に「おまけの動的プロパティ」を生やしたい場合 ➔ インデックスシグネチャ を使うのが自然です。
—
まとめ
いかがでしたか?
インデックスシグネチャも `Record
それぞれの特徴(キーの制限、他のプロパティとの同居しやすさ)を知ることで、ただ動くだけのコードから、「意図しないバグをコンパイル時に防いでくれる堅牢なコード」へとレベルアップできます。
ここをクリアできれば、TypeScriptの型システムとの距離がぐっと縮まりますよ。ぜひ明日のコーディングから試してみてくださいね!