noUncheckedIndexedAccess / TypeScript一人カレンダー
こんにちは、クレスウェア株式会社の奥野賢太郎 (@okunokentaro) です。本記事はTypeScript 一人 Advent Calendar 2024の11日目です。昨日は『実例 hooksTestingTools()』を紹介しました。
配列アクセス時のundefinedリスクを検出するオプション
TypeScript 4.1から--noUncheckedIndexedAccess
というコンパイラオプションが追加されています。これは、配列やタプルに添字アクセスした際、その要素が存在しない可能性を型で表現するオプションです。strict
を有効にしていても、このオプションはデフォルトで無効なため、別途明示的に有効化する必要があります。
筆者は初期からこのオプションを使っていますが、2年前のカレンダーではうっかり紹介を忘れていました。とはいえ、このオプションは実務において非常に有用なツールです。なぜなら、存在しない配列要素へのアクセス時にランタイムでundefined
が返る可能性を、コンパイル時に明示的に指摘してくれるからです。
無効時と有効時の挙動を比較
noUncheckedIndexedAccess
無効時の例を見てみましょう。
const arr = [0, 1, 2];
const v = arr[3];
// ^? number
console.log(v); // 型はnumberだが、実行時にはundefined
この状況では、arr[3]
は実際にundefined
を返しますが、型システムはnumber
であるとして扱います。実行時エラーを引き起こしかねない、危険な状況です。
一方、noUncheckedIndexedAccess
有効時は以下のように挙動が変わります。
const arr = [0, 1, 2];
const v = arr[3];
// ^? number | undefined
console.log(v); // undefinedの可能性ありとコンパイル時に判明
コンパイラはarr[3]
がnumber | undefined
であると推論し、strict
オプションと組み合わせていれば、undefined
の可能性を考慮せず利用しようとするときにエラーを起こします。これにより、潜在的なundefined
混入の危険性をコンパイル時点であぶり出すことができます。
undefinedチェックの手間を惜しまない
2年前のカレンダーで紹介したassertExists()
との組み合わせも有効です。
const arr = [0, 1, 2];
const v = arr[3];
assertExists(v);
console.log(v); // number型であることが確定する
ただ、こういった関数の利用が必要なことについて「毎回undefined
を考慮して行数が増えるのは面倒」と感じる人もいるかもしれません。とはいえ、実務上は想定外のundefined
が混入して問題が発生し、それを後から調査する手間のほうがよっぽど面倒だと思っています。コンパイラに警告されることで事前にエラー箇所を特定し、安全なコードを書くことができるのであれば、毎回検証する手間は掛けるに値するでしょう。
また、テストコードなどであれば、?.
(オプショナルチェーン演算子)を使ってundefined
を簡潔にハンドリングできます。以下はVitestのようなテストコードでよく使われる例です。この例ではNext.jsのredirect()
関数を適切に使用できているかを検証しています。
test("redirect先のパスが正しい", () => {
expect(redirectSpy.mock.calls[0]?.[0]).toEqual("/path/to");
});
noUncheckedIndexedAccess
が有効だとredirectSpy.mock.calls[0][0]
と書いた場合に、[0][0]
の箇所でエラーとなります。その結果、このように?.
を使う習慣がつき、予期せぬ実行時エラーを防ぐことができます。
as const も併用する
とはいえ、テストのモックデータを記述するときなどで「目の前にlength 2
であることが明らかな配列があるのに、undefined
のリスクを気にするのは冗長」という状況もあるでしょう。
type Item = {
id: string;
name: string;
}
function getName(item: Item): string {
// ...
}
const items = [
{ id: 'itemId0', name: 'itemName0' },
{ id: 'itemId1', name: 'itemName1' },
];
getName(items[0]); // モックの配列からモックデータを取り出す時はシンプルに書きたい
ここでは?.
も使えないためチェックが煩雑に感じるかもしれません。そんなときには、as const
を指定するのが便利です。
const items = [
{ id: 'itemId0', name: 'itemName0' },
{ id: 'itemId1', name: 'itemName1' },
] as const;
getName(items[0]); // 確実に取り出せるとわかるのでチェック不要
getName(items[2]); // 存在しないことが明らかであるため、コンパイルエラー
この指定を追加することでlength 2
の配列であることが確定し要素長が変化しないことが保証されるため、存在するindexであればエラーを出さず、存在しないindexのときにエラーを出すようになります。
テストのモックデータなどで、中身が変化しない固定の配列リテラルを作成する機会は多いと思いますので、こういった例ではas const
を積極的に付与していきましょう。
今すぐオプションの有効化を検討しよう
strict
オプションの有効化だけではnoUncheckedIndexedAccess
は有効になりません。もし現在、noUncheckedIndexedAccess
を有効にしていないのであれば、これを機に導入を検討してみるとよいでしょう。新規案件で一から開発環境を構築する際も、ここはうっかり忘れがちになるので気をつけたいところです。
明日は『satisfies』
本日は「noUncheckedIndexedAccess
」を紹介しました。それではまた。
Discussion