🐉 グォォォオオ! Hello,World!! ドラゴンだ!
🐉ちなみに好きな英単語の間に数字入れて略すワードは「i18n (internationalization : 国際化)」だぜ! k8sとかa16zとかどことなく厨二心をくすぐられる感じがいいよネ!
🐉 ...
🐉 ...(ちなみにS3みたいな略し方も好き )
※ k8s : プロダクショングレードのコンテナ管理基盤 - Kubernetesの略 ※ a16z : 米国を代表するトップクラスのVC、Andreessen Horowitz の略 ※ S3 : Amazonが提供するオブジェクトストレージサービス、Amazon Simple Storage Service の略
🐉今回はTypeScript書籍を読んでいて気になった「数値をキーに持つってケース結局いつなんだ?」ってことについてです!
🐉基本的に数値をキーに持つというコードはかかないため、そもそもキーに持てることを知らない人も多いかもしれません。筆者もへぇーと思いましたが、せっかくなので登場するケースを予習して、もう一歩踏み込んでみたいと思いました。
- TypeScript における「number がキーになる」ケースの使い分け
TypeScript における「number がキーになる」ケースの使い分け
TypeScript で数値を扱う方法は、配列やオブジェクトなど複数ありますが、具体的にどのように使い分けるとよいのでしょうか? 本記事では、
- インデックス操作(配列)
- タプル (Tuple)
- number がキーになるオブジェクト(Index Signature)
これらの違いと、どんなシーンで役立つのかを整理していきます。「存在は知っているけれど、実際どのときに使うのかピンとこない……」という方の参考になれば幸いです。
概要:知ってはいるが、いつ使うのか分からない?
TypeScript で配列やオブジェクトを扱うとき、
- インデックス操作 (
array[index]で要素を取得する方法) - タプル (tuple) (
[number, string]のように要素数や型が固定された配列型) - number がキーになるオブジェクト (
{ [key: number]: string }と型定義する方法)
など、数値を扱う方法は複数存在します。一見どれも似ているように見えますが、
- 「わざわざ number キーを使うメリットはあるの?」
- 「配列やタプルでよくないの?」
と感じる方も多いでしょう。そこで本記事では、まずそれぞれが何を意味するのかを整理し、どのような場面で使い分けるべきかを具体的に解説していきます。
1. そもそも「インデックス操作」「タプル」「number キーのオブジェクト」とは
1-1. インデックス操作
JavaScript でおなじみの「配列に対して array[index] でアクセスする」方法です。TypeScript では型チェックが入る点が異なりますが、基本的な使い方は同じです。
const numbers = [10, 20, 30]; // インデックス操作 const first = numbers[0]; // number
メリット
- JavaScript の標準的な配列操作なので、直感的に理解しやすい
- 要素数が可変な場合でも簡単に扱える
デメリット
- 要素数が固定ではないため、特定の位置の要素型を厳密に保証しづらい
numbers[10]のように存在しないインデックスを指定するとundefinedが返る可能性がある
1-2. タプル (Tuple)
タプルは「配列の要素数や各要素の型が固定されている配列型」です。例えば [number, string] であれば、1番目は number、2番目は string といった具合に厳密に定義できます。
type UserData = [number, string]; // 1番目: userId (number), 2番目: userName (string) const user: UserData = [1, 'Alice']; // OK const invalidUser: UserData = ['Bob', 2]; // エラー: 型が合わない
メリット
デメリット
- 要素数が固定なので、後から要素を増やしたり削除したりする操作には不向き
- 要素が増えると可読性が下がる
1-3. number がキーになるオブジェクト (Index Signature)
TypeScript では次のように、[key: number]: string; と記述することで、数値をプロパティキーとするオブジェクトを定義できます。
type NumberKeyObject = {
[key: number]: string;
};
const example: NumberKeyObject = {
0: 'Alice',
1: 'Bob',
// ...
};
一見、配列と似ていますが、「オブジェクトのプロパティとして number を使う」 点で異なります。JavaScript の仕様上、実際には数値のキーは文字列化されるため、example[0] は example["0"] と解釈されます。それでも TypeScript の型システム上、「number でアクセスするオブジェクト」 と表現できるのがポイントです。
メリット
- “キーが数値” というオブジェクト構造を型で表現できる
- 連想配列のように柔軟に追加情報を持たせることが可能
デメリット
- 実際にはキーは文字列として扱われるため、配列のように連続しているわけではない
- 存在しないキーを指定すると
undefinedが返ってくる可能性がある
2. 似ているけれど、どのように使い分ける?
数値でアクセスするという点ではどれも似ていますが、
といった観点で使い分けるのがおすすめです。以下では各ケースをコード例とともに見ていきます。
2-1. インデックス操作(配列)を使うべきケース
- 要素数が可変
- 順番が重要で、要素が連続している
- JavaScript の標準メソッド(
map,filter,reduce等)を活用したい
例えば「ユーザー ID を順番に管理する」「時系列でイベントを追加していく」など、順番が重要な場面では配列がもっとも自然です。
// 例: ユーザーIDの配列 const userIds: number[] = [1, 2, 3]; // 配列末尾に要素追加 userIds.push(4); // [1, 2, 3, 4] // インデックスでアクセス console.log(userIds[0]); // 1 // 標準的な配列メソッドを利用 const filteredUserIds = userIds.filter(id => id !== 2); console.log(filteredUserIds); // [1, 3, 4]
配列の利点は「要素が連続していて順序が大事」な場面に適していること。一方、存在しないインデックスにアクセスすると undefined になる点に注意が必要です。
2-2. タプル (Tuple) を使うべきケース
- 要素数が固定
- 「順番」と「型」が密接に関連している
例えば「(userId, userName) の 2 要素だけを確実にセットにしたい」「(x, y, z) のように座標を 3 要素で扱いたい」場合はタプルが最適です。
// (userId, userName) のタプル型を定義 type UserData = [number, string]; const user: UserData = [1, 'Alice']; // OK // エラー例: 順番と型が合わない const invalidUser: UserData = ['Alice', 1]; // Type 'string' is not assignable to type 'number'
タプルなら「先頭は必ず number、次は string」というように、コンパイラが要素ごとの型を厳密にチェックしてくれます。ただし、要素が多くなりすぎると可読性が落ちるため、タプルは「要素数が少なく、明確な順番と型が必要」な場面で使うのが理想です。
2-3. number がキーになるオブジェクトを使うべきケース
- オブジェクトとして管理しつつ、キーには数値を使いたい
- 配列の連続的な性質が不要
- 座席番号や商品コードのように「欠番」や「ランダムな番号」が発生しうる
例えば座席番号をキーにして、その席に座っている人の情報を格納したい場合、配列に連続的に詰めることもできます。しかし「1番・5番だけ埋まっていて、2〜4番は空席」といった欠番シナリオでは、number キーのオブジェクトのほうが自然に表現できます。
2-3-1. 座席管理の例
type SeatInfo = {
occupantName: string;
isVip: boolean;
};
type SeatMap = {
[seatNumber: number]: SeatInfo;
};
const seats: SeatMap = {
1: { occupantName: 'Alice', isVip: false },
5: { occupantName: 'Bob', isVip: true },
// 連続していない seatNumber でもOK
};
// 存在しない seatNumber へのアクセスは undefined
console.log(seats[2]); // undefined
2-3-2. スコアボードの例
// プレイヤーIDをキーにしてスコアを格納
type PlayerScoreBoard = {
[playerId: number]: number;
};
const scores: PlayerScoreBoard = {};
// プレイヤーID 10 番にスコア 50 を設定
scores[10] = 50;
// プレイヤーID 12 番にスコア 120 を設定
scores[12] = 120;
console.log(scores[10]); // 50
console.log(scores[999]); // undefined
2-3-3. 商品管理の例
type Product = {
name: string;
price: number;
};
type ProductMap = {
[productCode: number]: Product;
};
const products: ProductMap = {
1001: { name: 'Apple', price: 100 },
2002: { name: 'Banana', price: 80 },
};
// 数値キーなので productCode 毎に管理しやすい
console.log(products[1001].name); // "Apple"
配列でも同様の管理は可能ですが、「number をキーとしてデータを持つ」 という意図が明確な場合は、Index Signature を用いたオブジェクトのほうがより直感的になります。
3. まとめ:選択のポイント
TypeScript で「number をキーにする」方法は複数ありますが、それぞれ使いどころが異なります。ポイントをまとめると以下のとおりです。
インデックス操作(配列)
- 要素数が可変で連続的な並びを扱いたい
- 例: ユーザー ID のリスト、時系列のイベントログ
タプル (Tuple)
- 要素数が固定で、各要素の型や順番が明確に決まっている
- 例:
(userId, userName)や(x, y, z)のように要素の型が厳密に定義される
number キーのオブジェクト (Index Signature)
- 連続性は不要だが、数値をキーにした連想配列のような構造を使いたい
- 例: 座席番号、プレイヤーID、商品コードなど、欠番やランダムな番号が発生しうる場合
「わざわざ number キーを使うメリットは?」と思うかもしれませんが、配列とは異なる制約や管理しやすさを得られるのが強みです。どれを使うべきか迷ったら、
といった観点で検討しましょう。きっと これでTypeScript でのデータ扱いがさらに捗るはずです。