TypeScriptのコードレビューをしていて、最もエンジニアの「実力差」が露呈する瞬間の一つが、配列のインデックスアクセス、そしてそれに伴う `undefined` のハンドリングだ。
「`strictNullChecks: true` にしているから安全だ」と安易に `arr[i]` を叩き、ランタイムで `TypeError: Cannot read properties of undefined` を踏み抜く。あるいは、その恐怖から逃れるために、あらゆる場所で無意味なオプショナルチェーニング(`?.`)や、根拠のない型アサーション(`as string`)を乱発する——。
今日のコードレビューでは、この「配列のインデックスアクセスと `undefined` の向き合い方」について、コンパイラの型評価の裏側から実務のプロダクションコード設計まで、一切の妥協なくメスを入れる。
—
1. なぜ `arr[i]` はデフォルトで `undefined` を返すのか?
TypeScriptの歴史的背景と設計思想において、配列のインデックスアクセスが `T` ではなく `T | undefined`(`noUncheckedIndexedAccess` 有効時)を返すのは、JavaScriptのランタイムの現実と静的型安全性の整合性を保つための防壁である。
const list: string[] = [‘apple’, ‘banana’];
// strictNullChecks のみの場合、これは string と推論される(危険!)
const item = list[99];
もしあなたが `noUncheckedIndexedAccess: true` をtsconfigに設定していないのであれば、今すぐ設定してほしい。このフラグを有効にした瞬間、TypeScriptは配列の範囲外アクセスの可能性を検知し、型に `undefined` を強制的に付与する。
// tsconfig.json
{
“compilerOptions”: {
“strict”: true,
“noUncheckedIndexedAccess”: true
}
}
このフラグが有効な環境では、以下の型評価が行われる。
const colors = [‘red’, ‘green’, ‘blue’] as const;
type Color = (typeof colors)[number]; // “red” | “green” | “blue”
// 評価される型: “red” | “green” | “blue” | undefined
type DangerousColor = typeof colors[number];
ランタイムの配列は可変長であり、TypeScriptのコンパイラは静的なコード解析だけでは「そのインデックスに確実に要素が存在するかどうか」を常に追跡できるわけではない(特に動的なループ変数やユーザー入力値の場合)。したがって、コンパイラは「存在しないかもしれない」という現実を受け入れ、`undefined` を型に含めるのだ。
—
2. 現場でよく見る「最悪なアンチパターン」
レビューでよく見かける、保守性を破壊する悪手を確認しておこう。
アンチパターン A: 根拠なき型アサーション(`as` の暴力)
// ❌ 絶対にやってはいけない
const firstItem = items[0] as string;
コンパイラを黙らせるために `as` を使うのは、型安全性の放棄に他ならない。配列が空だった場合、バグはコンパイル時ではなくプロダクション環境のユーザーの画面で爆発する。
アンチパターン B: 無慈悲な非nullアサーション(`!` 演算子)
// ❌ リファクタリング耐性がゼロになる
const target = users[currentIndex]!;
「ここは絶対に存在することが分かっているから」という開発者の思い込みほど当てにならないものはない。将来の仕様変更で配列の長さが変わった瞬間、このコードはSilent Failure(沈黙した障害)を引き起こす。
—
3. 【プロダクションコード】堅牢なインデックスアクセス設計
では、実務のフロントエンド開発やAPI連携において、どのように設計し、実装すべきか。
「タプル型による厳密な制限」「ガード関数の活用」「安全なユーティリティの構築」を網羅した実用的なコードを示す。
/
- プロダクションクオリティの配列・タプル安全ハンドリング
/
// 1. 固定長が保証されたデータには「タプル型」を強制する
// 緯度・経度のように要素数が決まっている場合は、配列 (number[]) ではなくタプル ([number, number]) を使う。
type Coordinates = [latitude: number, longitude: number];
function processGeo(coords: Coordinates) {
// タプルに対するインデックスアクセスは、noUncheckedIndexedAccess下でも undefined を含まない(要素数が型で保証されているため)
const lat = coords[0]; // 型は number
const lng = coords[1]; // 型は number
return { lat, lng };
}
// 2. 可変長配列に対する「ユーザー定義タイプガード」の活用
function isDefined
return value !== undefined;
}
// APIから取得した可変長データリストの想定
interface Article {
id: string;
title: string;
}
function renderLatestArticles(articles: Article[]) {
// インデックスアクセスで安全に取得しつつ、undefinedを除外する
const primaryArticle = articles[0];
const secondaryArticle = articles[1];
// 早期リターンによるガード
if (!isDefined(primaryArticle)) {
return / フォールバックUIの描画 /;
}
// ここでは primaryArticle は Article 型に絞り込まれている
console.log(primaryArticle.title);
// 配列の安全なマッピングとフィルタリングの組み合わせ
const validSecondaryTitles = [secondaryArticle]
.filter(isDefined)
.map(art => art.title);
return validSecondaryTitles;
}
// 3. 境界チェックをカプセル化する安全なアクセサ関数
// 配列の安全性とコードの可読性を両立させるためのチーフアーキテクト推奨パターン
function safeAt
// 負のインデックス(Python的アプローチ)や範囲外を安全に処理
const normalizedIndex = index < 0 ? array.length + index : index;
if (normalizedIndex < 0 || normalizedIndex >= array.length) {
return undefined;
}
return array[normalizedIndex];
}
// — 実際の使用例 —
const tags = [‘typescript’, ‘react’, ‘architecture’] as const;
// 範囲内の安全なアクセス
const secondTag = safeAt(tags, 1); // 型は “typescript” | “react” | “architecture” | undefined
if (secondTag) {
// 厳密に型が絞り込まれる
console.log(`Selected tag: ${secondTag.toUpperCase()}`);
}
—
4. パフォーマンスと型評価のコストに関する知見
ここで、シニアエンジニアとしてパフォーマンスの観点にも言及しておこう。
1. ランタイムのオーバーヘッド:
`safeAt` のようなラッパー関数や `.filter(isDefined)` を挟むことによるパフォーマンス低下は、現代のV8エンジン(JITコンパイラ)の前では実質的に無視できるレベル(数ナノ秒オーダー)だ。可読性と堅牢性のメリットが圧倒的に勝る。
2. 型チェックのコスト(コンパイルタイム):
過剰なジェネリクスや複雑な条件付き型のネストは、TSC(TypeScript Compiler)の型推論フェーズを重くし、IDEのサジェスト遅延(Type-Check Latency)を招く。配列のアクセスにおいては、複雑な型パズルを組むのではなく、`noUncheckedIndexedAccess` を有効にした上で、上記のようなシンプルかつプリミティブなガード関数を組み合わせるのが、コンパイラにとっても人間にとっても最も健やかなアプローチである。
—
チーフアーキテクトからの総括
配列のインデックスアクセスにおける `undefined` を型に含めるべきか否かという問いに対する答えは明快だ。「含めるべきであり、その事実から逃げてはならない」。
TypeScriptは、JavaScriptのダイナミズムが生むランタイムエラーという名の地雷原を、静的な型システムによって安全な遊歩道に変えるためのツールだ。`undefined` を型から排除しようと `as` や `!` で目を背けるのではなく、言語の仕様と真正面から向き合い、ガード節やタプル型を用いて「安全にハンドリングされた状態」をコードで表現し尽くすこと。
それこそが、大規模開発を破綻させない、真に保守性の高いプロダクションコードを築く唯一の道である。次回のコードレビューでは、チームメンバーの `arr[i]` の使い方を鋭く、かつ温かく見守ってあげてほしい。