問題の背景:
TypeScriptの進化に伴い、論理分岐の中で変数のより狭い型を確実に特定する必要が生じてきました。従来の型チェック(typeofやinstanceofを介して)は、特にオブジェクトが複雑な構造や階層を持つ場合には十分ではありません。データの安全性と利便性を高めるために、TypeScriptはユーザー定義の型ガードを作成するための型述語メカニズムを実装しました。
問題:
関数内の通常の型チェックは、true/falseの結果を使った場合、コンパイラに変数の型に関する情報を提供しません。コンパイラは何が確認されたかを「理解」していないため、存在しないプロパティに誤ってアクセスしたときにランタイムでの暗黙のエラーが発生します。
解決策:
型述語(type predicates)は、'param is Type'という代入型を使用することで、コンパイラがこのパラメータを型チェックの後で特定の型として扱えることを理解します。このような関数は型の安全性を高め、複雑な問題に対する型の絞り込みシステムを拡張します。
コード例:
interface Bird { fly(): void; feathers: boolean; } interface Fish { swim(): void; fins: number; } function isBird(animal: Bird | Fish): animal is Bird { return (animal as Bird).fly !== undefined; } const pet: Bird | Fish = ...; if (isBird(pet)) { pet.fly(); // OK: petは今Bird } else { pet.swim(); // OK: petは今Fish }
主な特徴:
type guard関数は、戻り値の型 'param is Type' を明示的に指定しなくても機能しますか?
いいえ、シグネチャに明示的に 'param is Type' を指定しない場合、TypeScriptはコードの分岐において型を絞り込むことができません。コンパイラは、そのパラメータを特定の型として使用できることを理解しません。
コード例:
function isFish(animal: Fish | Bird): boolean { return (animal as Fish).swim !== undefined; } // 動作しますか? if (isFish(pet)) { pet.swim(); // エラー: Property 'swim' does not exist }
型述語を使用して文字列や数値のようなプリミティブ値をチェックできますか?
はい、可能ですが、通常はtypeofを使用し、これらのガードは冗長になります。それでも、ユーザー定義のガードを実装することを妨げるものはありません:
function isString(x: unknown): x is string { return typeof x === "string"; }
type guard関数は、コンパイル時に型のエラーから厳密に保護しますか?
完全にはそうではありません。TypeScriptは関数の実装に依存し、内部のロジックの正当性を確認することはできません。チェックを誤って実装すると、コンパイラはエラーを理解せず、実行時に問題が発生することになります。
function isFish(animal: Fish | Bird): animal is Fish { // 誤って: 常にtrueを返す return true; }
ネガティブケース 開発者は構造のチェックにエラーを持つプレディケート関数を実装し、その結果、関数は常にtrueを返しました。コードはコンパイルを通過しましたが、ランタイム時に存在しないメソッドが呼び出されました。
メリット:
デメリット:
ポジティブケース Type predicate関数は正しく実装され、境界値やエラーケースでユニットテストされています。
メリット:
デメリット: