TypeScriptの型システムは強力だが、その恩恵を最大限に引き出せているチームは意外と少ない。特に、日々の開発で何気なく書いている配列のインデックスアクセスは、TypeScriptの静的型安全性における最大の「隠し穴」の一つだ。
コードレビューをしていて、次のようなコードを見過ごしていないだろうか?
const users = [‘Alice’, ‘Bob’];
const user = users[10]; // 型は string と推論される
console.log(user.toUpperCase()); // 💥 実行時エラー: Cannot read properties of undefined (reading ‘toUpperCase’)
なぜ `users[10]` が `string` 型になってしまうのか。そして、なぜこれがプロダクション環境で致命的なバグを生むのか。
今回は、TypeScriptの隠れた牙城である `noUncheckedIndexedAccess` を完全に手なずけ、コンパイルタイムでランタイムエラーを根絶する極限の型設計術を伝授する。
—
なぜデフォルトの配列アクセスは「嘘」をつくのか
TypeScriptの初期設計において、配列のインデックスアクセス(`array[i]`)は、利便性とJavaScript(ES2015)からの移行コストを考慮し、「インデックスに存在する要素の型」をそのまま返す仕様になっていた。
つまり、要素数2の配列であっても、存在しない100番目にアクセスした際の戻り値は `undefined` であるにもかかわらず、型は `string` として推論されてしまう。これがTypeScriptにおける最大の「虚偽表示」だ。
この仕様のせいで、開発者は常に「ここに値が存在するはずだ」という暗黙の仮定(魔術的な前提)に頼ることになり、APIレスポンスの欠損や、動的なフィルタリング後の配列操作で、本番環境でのクラッシュを引き起こす。
—
救世主:`noUncheckedIndexedAccess` の真実
この言語仕様の「嘘」を強制的に正すコンパイラオプションが `noUncheckedIndexedAccess` である。
`tsconfig.json` の `compilerOptions` にこれを有効化するだけで、世界が変わる。
{
“compilerOptions”: {
“strict”: true,
“noUncheckedIndexedAccess”: true
}
}
このフラグを有効にした瞬間、配列やタプルのインデックスアクセス、および数値インデックスを持つオブジェクトの戻り値には、強制的に `undefined` がユニオン型として付与される。
const users = [‘Alice’, ‘Bob’];
const user = users[10];
// 型は string | undefined に化ける!
「すべてのアクセスに対して `if (user)` やOptional Chainingを書くのが面倒臭い」と感じただろうか?
それこそがTypeScriptがあなたに強制したかった「安全性のコスト」である。 存在しないかもしれない要素にそのままアクセスすること自体がバグの温床なのだから、コンパイラに怒られるべきなのだ。
—
実務で即採用できる:堅牢な配列操作の設計パターン
では、`noUncheckedIndexedAccess: true` という厳格な世界で、どのようにフロントエンドのコンポーネント設計や非同期API連携を構築すべきか。プロダクションコードでそのまま使える実践パターンを提示する。
1. ユーザー定義型ガード(User-Defined Type Guards)による絞り込み
配列から値を取り出し、かつ安全に後続処理を行いたい場合、単なる `undefined` チェックを超えて、型安全性を担保するガード関数をユーティリティとして持たせるのがシニアの作法だ。
/
- 配列のインデックスアクセスから undefined を排除する型ガード
/
export function isDefined
return value !== undefined;
}
// 実際のAPIレスポンス処理の例
interface ApiResponse {
items: string[];
}
function processResponse(res: ApiResponse) {
// どのインデックスにアクセスするか不明な場合
const rawTarget = res.items[0]; // string | undefined
// 型ガードを通すことで、以降のスコープで string が保証される
if (isDefined(rawTarget)) {
console.log(rawTarget.toUpperCase()); // 安全!
}
}
2. 「安全な配列アクセサ(Safe Indexer)」の抽象化
毎回 `isDefined` を書くのが冗長な場合、インデックスアクセスをラップしたユーティリティ関数をプロジェクトの基盤層(`utils/array.ts` 等)に用意する。
/
- 境界チェックを内包した安全な配列アクセサ
- 配列の範囲外や undefined の混入をコンパイル&実行時に防ぐ
/
export function safeAt
return array[index];
}
// — 使用例: 複雑なUIコンポーネントでのタブ切り替え状態管理 —
const tabs = [‘profile’, ‘settings’, ‘billing’] as const;
type TabType = typeof tabs[number];
function handleTabSelection(index: number): void {
// safeAt を使えば、意図しないインデックス指定に対しても型安全にガードできる
const selectedTab = safeAt(tabs, index);
if (!selectedTab) {
console.warn(`Invalid tab index: ${index}`);
return;
}
// ここでは selectedTab は “profile” | “settings” | “billing” に絞り込まれている
renderTabContent(selectedTab);
}
function renderTabContent(tab: TabType) {
// 描画処理
}
3. タプル型(Tuples)との美しい共生
APIから固定長の配列(例:緯度経度のペア `[number, number]` など)が返ってくる場合、`noUncheckedIndexedAccess` はタプルに対しても厳格に機能する。
// 緯度・経度のタプル
type Coordinates = [latitude: number, longitude: number];
function parseGeoData(raw: number[]): Coordinates | null {
// raw[0], raw[1] は number | undefined になるため、そのままタプルには代入できない
const lat = raw[0];
const lng = raw[1];
if (lat === undefined || lng === undefined) {
return null; // 不完全なデータは弾く
}
// ここで初めて [number, number] として確定する
return [lat, lng];
}
このアプローチにより、「APIから配列が返ってきたからとりあえず `[0]` と `[1]` をぶち込む」という脆弱なコードを完全に駆逐できる。
—
パフォーマンスとアーキテクチャ上の注意点
`noUncheckedIndexedAccess` を導入する際、開発者からよく出る懸念が「パフォーマンスへの影響」だ。
結論から言えば、コンパイル時の型チェックコストがごくわずかに増加する以外、ランタイムのパフォーマンスへの悪影響はゼロである。TypeScriptの型情報はトランスパイル時にすべて消去されるため、追加される `undefined` チェックは、あなたが記述した `if` 文や Optional Chaining(`?.`)に依存する。
むしろ、実行時エラー(TypeError)によるクラッシュや、それを回避するための冗長なtry-catch、防衛的コードの乱雑さを排除できるため、コードベース全体のメンテナビリティは劇的に向上する。
—
チーフアーキテクトからの提言
多くのチームが `strict: true` さえ入れておけば安全だと錯覚している。だが、`noUncheckedIndexedAccess` を有効にしていないTypeScriptコードベースは、いわば「シートベルトをしていないスポーツカー」だ。高速にコードを書けたとしても、いつ曲がりきれずに壁に激突するか分からない。
今日からあなたのプロジェクトの `tsconfig.json` に `noUncheckedIndexedAccess: true` を追加しよう。
コンパイラが赤く吐き出すエラーの数々は、これまでの開発で見過ごしてきた「潜在的バグの山」に他ならない。それを一つずつ型安全なコードでリファクタリングしていくことこそが、真に堅牢なプロダクションコードを作り上げる唯一の王道である。