【テクニカル・上級編】配列の要素を型として抽出する:typeofとインデックスアクセス型の応用 – TypeScript コア・型システムの基礎解析バイブル

[Deep Dive] 配列から真理を抽出せよ:インデックスアクセス型と const アサーションによる型レベルの「単一真理値性」

現代の堅牢なアプリケーション・アーキテクチャにおいて、最も回避すべきは「情報の重複」と、それに伴う「型と実態の乖離(Type-Reality Decoupling)」である。

シニアエンジニアであれば、実行時の `String` 型が、実際には特定のプロトコルコマンドやステータスコードの集合であることを知っているはずだ。しかし、それを `type Status = “pending” | “success” | “failed”` と手書きで定義し、同時に `const statuses = [“pending”, “success”, “failed”]` という配列を定義した瞬間、あなたのコードには「保守の敗北」が組み込まれる。

本稿では、TypeScript コンパイラの挙動を掌握し、配列という実行時の実体から、コンパイルタイムの純粋な型情報を抽出する極限の知見を共有する。

—

1. `as const`:リテラルをヒープから「型空間」へ固定する

TypeScript において、通常の配列宣言は「可変(Mutable)」と推論される。

// 推論される型: string[]
const ROLES = [“admin”, “editor”, “viewer”];

この時、コンパイラはメモリ効率と柔軟性を優先し、要素が将来的に変更される可能性を考慮して型を「広げる(Widening)」。しかし、我々が求めているのは「実行時の値」そのものを「型」として固定することだ。ここで `as const`(const アサーション)を投入する。

// 推論される型: readonly [“admin”, “editor”, “viewer”]
const ROLES = [“admin”, “editor”, “viewer”] as const;

`as const` は単なる `readonly` 化ではない。コンパイラに対し、「この配列の各要素を、具象的なリテラル型として扱え」という強力な指示を出す。これにより、配列は単なるデータの塊から、厳密な型定義のソースへと昇華する。

—

2. `T[number]`:インデックスアクセス型によるユニオン展開の力学

次に、この配列からユニオン型を抽出する。ここで使用するのが「インデックスアクセス型(Indexed Access Types)」である。

const ROLES = [“admin”, “editor”, “viewer”] as const;

/

  • ROLES[number] の挙動:
  • 配列のインデックス(number)にアクセス可能なすべての要素の型を、
  • ユニオン型として合成する。

/
type Role = (typeof ROLES)[number];

// Role 型は “admin” | “editor” | “viewer” と等価になる

なぜ `[number]` なのか。TypeScript において配列は、数値(インデックス)をキーに持つオブジェクトの特殊な形態である。`ROLES[0]` は `”admin”` を返すが、`ROLES[number]` は「すべての数値キーに対応する値の合併(Union)」を計算する。

これはコンパイラの `TypeChecker` が、配列のシグネチャを走査し、再帰的に型を畳み込むプロセスである。

—

3. 実践:ランタイムとコンパイルタイムの完全な同期

この手法の真価は、セキュリティクリティカルなバリデーションや、イベントループのディスパッチャで発揮される。

// 1. 真理のソース(Source of Truth)を定義
const SYSTEM_COMMANDS = [“CONNECT”, “DISCONNECT”, “TRANSFER”, “UPGRADE”] as const;

// 2. 自動的に型を生成(手動メンテナンス不要)
type SystemCommand = (typeof SYSTEM_COMMANDS)[number];

/

  • 3. ランタイムでのガード関数
  • 型定義と実行時のチェックが、常にこの配列一つに集約される。

/
function isValidCommand(cmd: string): cmd is SystemCommand {
// 配列のメモリ空間を直接参照し、計算量 O(N) で検証
return (SYSTEM_COMMANDS as readonly string[]).includes(cmd);
}

// 4. 利用例
function execute(command: string) {
if (isValidCommand(command)) {
// このブロック内では、command は SystemCommand 型として完全に保証される
processPayload(command);
} else {
throw new Error(`Security Violation: Invalid Command [${command}]`);
}
}

このパターンを採用することで、「新しいコマンドを追加したが、型定義の更新を忘れてコンパイルが通ってしまう」という初歩的かつ致命的なミスを物理的に排除できる。

—

4. アーキテクトの視点:低レイヤでの考察

メモリと最適化

`as const` で定義された配列は、V8 エンジンなどのランタイムにおいて「Constant Field」として最適化の対象になりやすい。また、TypeScript のコンパイラレベルでは、巨大なユニオン型(数千要素を超えるもの)を生成する場合、型推論のスタック消費に注意が必要だ。しかし、通常の業務ロジックにおける数百程度の要素であれば、手書きのユニオン型よりも、配列からの抽出の方がコンパイラのキャッシュ効率が良い場合が多い。

セキュリティ:Type Confusion の防止

外部入力(JSON API や Webhook)を扱う際、型定義を配列から生成する手法は、「ホワイトリストの強制」を意味する。`as const` 配列を `ReadOnly` として扱うことで、実行時にプロトタイプ汚染(Prototype Pollution)などで配列が書き換えられるリスクを最小化し、静的解析の防壁を突破させない堅牢なコードベースを構築できる。

—

5. 高度な応用:オブジェクト配列からの抽出

さらに一歩進んで、複雑な定義体から特定のプロパティのみを型として抽出することも可能だ。

const CONFIGS = [
{ id: 1, code: ‘ERR_AUTH’, severity: ‘HIGH’ },
{ id: 2, code: ‘ERR_NETWORK’, severity: ‘MEDIUM’ },
{ id: 3, code: ‘ERR_DB’, severity: ‘CRITICAL’ },
] as const;

// 各要素の ‘code’ プロパティだけを抽出してユニオン型にする
type ErrorCode = (typeof CONFIGS)[number][‘code’];

// ErrorCode は “ERR_AUTH” | “ERR_NETWORK” | “ERR_DB”

これは、大規模なマイクロサービス群におけるエラーコードの管理や、フロントエンドにおける動的なルーティング定義において、型安全性を維持するための「究極の武器」となる。

結論

`typeof` と `[number]` の組み合わせは、単なるシンタックスシュガーではない。それは、「実行時のデータ構造」と「コンパイル時の型定義」を、数学的な写像によって完全に一致させる儀式である。

真のアーキテクトは、コードを書かない。コードを生成する「構造」を設計する。配列から型を抽出するこの手法を血肉化し、あなたのシステムから「同期不全」という名の脆弱性を根絶せよ。

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