こんにちは!フロントエンドからバックエンドまで、TypeScriptの荒波を日々一緒に航海しているシニアアーキテクトの私です。
TypeScriptを書き始めると、「あれ、この配列の中身の型だけをうまく取り出して、別の場所でも使い回したいな」という瞬間に出会いませんか?
例えば、APIから返ってくるデータのリストや、画面のタブメニューの選択肢などを定義したとき、配列の要素型をわざわざ別で書き直したりしていませんか?それ、DRY原則(Don’t Repeat Yourself)に反しちゃっていますよね。
今回は、TypeScriptの強力な機能であるIndexed Access Types(インデックスアクセス型)を駆使して、配列の要素型を自由自在に抽出・再利用するテクニックを優しく、そして本質から解説していきます。
ここをクリアすれば、あなたのTypeScriptの型定義の引き出しはグッと広がり、コードの保守性が劇的に向上しますよ。さあ、一緒にマスターしていきましょう!
—
1. なぜ「型の重複」は悪なのか?(DRY原則の重要性)
まずは、よくあるこんなコードを見てみてください。
// ユーザーの役割(ロール)のリストを定義した配列
const ROLES = [‘admin’, ‘editor’, ‘viewer’] as const;
// ❌ 悪い例:リテラル型をわざわざもう一度手動で定義している
type Role = ‘admin’ | ‘editor’ | ‘viewer’;
これの何が問題か分かりますか?
もし将来、新しい役割として `’moderator’` を追加したくなったとき、`ROLES` 配列を修正し、さらに `Role` 型の定義も手動で書き直さなければなりません。
「あ、型の書き換えを忘れてバグが出た……!」
そんな悲劇を生まないために、TypeScriptには「配列から自動的に型を逆算する」という超スマートな仕組みが用意されています。それが今回主役の Indexed Access Types です。
—
2. Indexed Access Types の基本の「キ」
TypeScriptのIndexed Access Typesとは、JavaScriptでオブジェクトのプロパティにアクセスする構文(`obj[‘key’]`)の「型版」です。
配列の場合、「配列の型[数字の型]」と書くことで、その位置にある要素の型を取り出すことができます。
百聞は一見に如かず。コードで見てみましょう。
// タプル型(要素の数が固定された配列)の例
type Point = [number, number];
// 0番目の要素(X座標)の型を取り出す
type XCoord = Point[0]; // ➡️ 評価結果: number
// 1番目の要素(Y座標)の型を取り出す
type YCoord = Point[1]; // ➡️ 評価結果: number
このように、型に対してもブラウザのコンソールでプロパティを覗き見るかのように、ピンポイントで型を抽出できるのがIndexed Access Typesの正体です。
—
3. 実践!`as const` と組み合わせた配列の要素型抽出
さて、ここからが本番です。先ほどの `ROLES` の例を、最もモダンで美しい形に書き換えてみましょう。
ここで登場するのが、`as const`(constアサーション)という魔法の構文です。これをつけることで、単なる「文字列の配列(`string[]`)」から、「変更不可能な、それぞれの値がリテラル型であるタプル」へと昇格させることができます。
// 1. as const を使って「読み取り専用の厳格な配列」を作る
const ROLES = [‘admin’, ‘editor’, ‘viewer’] as const;
// 2. 配列の型そのものを取得する: typeof ROLES
// これの型は readonly [“admin”, “editor”, “viewer”] になります
// 3. [number] を使って、配列の「すべての要素のどれか」を一括抽出する!
type Role = typeof ROLES[number];
// ➡️ 評価結果: “admin” | “editor” | “viewer”
💡 この魔法(`[number]`)はどう動いているの?
「あれ? `number` ってインデックスに指定してどういうこと?」って思いますよね。
JavaScriptの配列のインデックスは数値(number)です。そのため、TypeScriptの型システムに対して `typeof ROLES[number]` と指定すると、「この配列のインデックス(数値すべて)にヒットする要素の型を全部ユニオン型(合体)として教えて!」という意味になります。
結果として、個別に書き直すことなく、配列から完璧なユニオン型を自動生成できるというわけです。最高にエレガントですよね!
—
4. 初学者が陥りがちな「文法エラー」と落とし穴
ここで、現場でよくある失敗パターンをいくつかご紹介しておきます。これを覚えておけば安心です。
⚠️ エラーパターン1: `as const` を付け忘れる
const FRUITS = [‘apple’, ‘banana’, ‘orange’]; // as const がない!
type Fruit = typeof FRUITS[number];
// ➡️ 評価結果: string (”apple” | “banana” | “orange” にならない!)
解説:
`as const` がないと、TypeScriptは「あ、これは後から要素が追加・変更される普通の文字列の配列ね」と判断するため、抽出される型は単なる `string` になってしまいます。リテラル型を取り出したいときは、必ず `as const` をセットで使いましょう。
⚠️ エラーパターン2: 値のつもりで型を書こうとして混乱する
const ROLES = [‘admin’, ‘editor’] as const;
// ❌ 誤った書き方
type BadRole = ROLES[number]; // 型空間で値を使おうとしてエラーになることがある
解説:
TypeScriptのコードは、「値の世界(実行されるコード)」と「型の世界(コンパイル時だけに存在するもの)」に分かれています。`typeof` を使ってうまく値の世界から型の世界に橋渡しをしてあげる必要がある点を忘れないようにしましょう。
—
5. 実務で即効性を発揮するユースケース
このテクニックが一番輝くのは、次のような「設定値からコンポーネントのPropsを導き出す」ようなシーンです。
// タブの選択肢リスト
const TABS = [‘profile’, ‘settings’, ‘security’] as const;
// 配列から自動生成されたタブの型
type TabType = typeof TABS[number]; // “profile” | “settings” | “security”
interface TabButtonProps {
currentTab: TabType;
onSelect: (tab: TabType) => void;
}
// これにより、間違ったタブ名が渡された瞬間にコンパイルエラーで弾くことができます!
もし将来、新しいタブ `’billing’` が追加されても、`TABS` 配列を1箇所書き換えるだけで、関連するすべての型やコンポーネントのインターフェースが自動的に追従してくれます。これぞ、保守性の高い堅牢なTypeScriptコードです。
—
おわりに
今回は、Indexed Access Typesを用いた配列の要素型の動的な取得と、DRY原則の徹底について解説しました。
- `as const` で配列を厳格なタプル型にする
- `typeof 数组[number]` で要素のユニオン型をエレガントに抽出する
この2つのコンボを使いこなせるようになると、型定義の重複に悩まされることがグッと減り、コードを書くのがもっと楽しくなりますよ。
ここをクリアしたあなたなら、もうTypeScriptの基本の型システムはバッチリマスターできています!自信を持って、日々の開発にこのテクニックを取り入れてみてくださいね。それでは、次の記事でお会いしましょう!