非nullアサーション(!)がコンパイラを騙して実行時エラーになる
変数名の後ろに!を付ける非nullアサーションは、「この値はnull/undefinedではない」とコンパイラに約束する構文です。実際にはundefinedだった場合、型チェックは通過しますが実行時に例外が発生します。
エラーメッセージの読み方
main.ts:2
main.ts- ファイル名 — コンパイル前のTypeScriptソースです(sourceMapで変換後の位置を逆引きしています)
2- 行番号 — 実際にクラッシュした行
TypeError- 例外クラス — 何が起きたか。ここを検索するのが最短です
Cannot read properties of undefined (reading 'toUpperCase')- 内容 — 期待していたもの、または受け付けられなかったもの
このエラーが出る典型パターン
パターン1
1 function shout(s: string): string { 2 return s.toUpperCase(); 3 } 4 const items: string[] = ["a", "b"]; 5 const target: string | undefined = items[5]; ^ 6 console.log(shout(target!));
main.ts:2
TypeError: Cannot read properties of undefined (reading 'toUpperCase')
items[5]は範囲外なのでundefinedです。target!は「undefinedではない」とコンパイラに約束するだけで、実際の値までは変えません。呼び出し時に例外が起きます。
直し方: 5 を 0 にします。
広告
広告スロット(未設定)
パターン2
1 interface User { 2 name: string; 3 } 4 function findUser(id: number): User | undefined { 5 if (id === 1) return { name: "Al" }; 6 return undefined; 7 } 8 const u = findUser(2); ^ 9 console.log(u!.name.toUpperCase());
main.ts:9
TypeError: Cannot read properties of undefined (reading 'toUpperCase')
id=2ではfindUserがundefinedを返します。u!はコンパイラの検査を止めるだけなので、実行時にはundefinedのままnameへアクセスして例外になります。
直し方: 2 を 1 にします。
パターン3
1 function toLength(items: number[] | undefined): number { 2 return items!.length; 3 } 4 const data: number[] | undefined = undefined; ^ 5 console.log(toLength(data));
main.ts:2
TypeError: Cannot read properties of undefined (reading 'length')
dataがundefinedのままitems!でアクセスすると、実行時にlengthを読もうとして例外になります。
直し方: undefined を [1, 2, 3] にします。
よくある誤解
「型チェックを通過したのだから安全なはず」という思い込みは、!を使った箇所には当てはまりません。!はコンパイラの検査を黙らせるだけで、実行時の値そのものを保証するものではありません。
まとめ
非nullアサーション(!)がコンパイラを騙して実行時エラーになるは初級でつまずきやすい項目です。上の3パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。
関連するエラー
- asによる型アサーションがコンパイラを騙して実行時エラーになるasはコンパイラに「この値はこの型として扱ってよい」と伝える構文で、実際の値を変換したり検証したりはしません。
- as unknown as Xの二重キャストは型チェックを完全に迂回し、実行時エラーの原因になるTypeScriptは通常、明らかに無関係な型同士のasによるキャストをエラーにします。
- is possibly 'undefined' の原因と直し方interfaceで?を付けたオプショナルプロパティは、値が入っていない(undefined)可能性を型として保持します。