Code Fix

中級

asによる型アサーションがコンパイラを騙して実行時エラーになる

asはコンパイラに「この値はこの型として扱ってよい」と伝える構文で、実際の値を変換したり検証したりはしません。unknown型の値をasで別の型に見せかけると、実際の値がその型のメソッドを持たない場合に実行時エラーになります。

エラーメッセージの読み方

main.ts:2
main.ts
ファイル名 — コンパイル前のTypeScriptソースです(sourceMapで変換後の位置を逆引きしています)
2
行番号 — 実際にクラッシュした行
TypeError
例外クラス — 何が起きたか。ここを検索するのが最短です
s.toUpperCase is not a function
内容 — 期待していたもの、または受け付けられなかったもの

このエラーが出る典型パターン

パターン1

 1  function shout(s: string): string {
 2    return s.toUpperCase();
 3  }
 4  const data: unknown = 42  ;
                            ^
 5  console.log(shout(data as string));
main.ts:2 TypeError: s.toUpperCase is not a function

dataは実際にはnumberです。as stringはコンパイラを黙らせるだけで、実行時にはnumberにtoUpperCaseは存在せず例外になります。

直し方: 42 を "hi" にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  interface User {
 2    name: string;
 3  }
 4  function greet(u: User): string {
 5    return u.name.toUpperCase();
 6  }
 7  const raw: unknown = 42            ;
                                ^
 8  console.log(greet(raw as User));
main.ts:5 TypeError: Cannot read properties of undefined (reading 'toUpperCase')

rawは実際には数値です。as Userで型を偽装しても、実行時にはnameプロパティが存在せずundefinedになります。

直し方: 42 を { name: "Al" } にします。

この問題を解いてみる →

パターン3

 1  function sumAll(items: number[]): number {
 2    return items.reduce((a, b) => a + b, 0);
 3  }
 4  const raw: unknown = "5"      ;
                             ^
 5  console.log(sumAll(raw as number[]));
main.ts:2 TypeError: items.reduce is not a function

rawは実際には文字列です。as number[]で型を偽装しても、文字列にreduceメソッドは存在しないため例外になります。

直し方: "5" を [1, 2, 3] にします。

この問題を解いてみる →

パターン4

 1  function shout(s: string): string {
 2    return s.toUpperCase();
 3  }
 4  const data: unknown = true;
                            ^
 5  console.log(shout(data as string));
main.ts:2 TypeError: s.toUpperCase is not a function

dataは実際にはbooleanです。as stringはコンパイラを黙らせるだけで、実行時にはbooleanにtoUpperCaseは存在せず例外になります。

直し方: true を "hi" にします。

この問題を解いてみる →

パターン5

 1  interface Car {
 2    engine: { start(): string };
 3  }
 4  function ignite(c: Car): string {
 5    return c.engine.start();
 6  }
 7  const raw: unknown = 5                                   ;
                                           ^
 8  console.log(ignite(raw as Car));
main.ts:5 TypeError: Cannot read properties of undefined (reading 'start')

rawは実際には数値です。as Carで型を偽装しても、実行時にはengineプロパティが存在せずundefinedになります。

直し方: 5 を { engine: { start: () => "vroom" } } にします。

この問題を解いてみる →

よくある誤解

「asで型を指定したのだから、その型のはず」という思い込みは危険です。asは型チェックを通すための宣言に過ぎず、実際の値がその型の性質を満たしているかどうかは実行時まで分かりません。

実務での勘所

asは完全に無制限というわけではありません。TypeScriptは、変換元と変換先の型が構造的にまったく重なりを持たない場合(たとえば"hello" as numberのように文字列を直接numberとして扱おうとする場合)は、これをコンパイルエラーとして拒否します。asが許されるのは、多少なりとも型の構造に重なりがある場合に限られます。ただし、この制約はunknownを経由する二重のasによって迂回できてしまうという弱点があります。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)