🐉 グォォォオオ! 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 でのデータ扱いがさらに捗るはずです。