質問の背景:TypeScriptは通常の型チェックを拡張し、開発者がオブジェクトが特定の型に従っているかどうかを確認するためのユーザー定義の関数、すなわちタイプガードを作成できるようにします。これは、型値が異なる可能性があるユニオン型、動的構造、およびAPIで作業するために必要です。
通常のtypeofやinstanceofによる型チェックはプリミティブとクラスに制限されているため、構造や複雑な型はこれだけでは特定できません。コンパイラに対してどのケースで値が必要な型に安全に絞られるかを明示的に示す必要があります。
解決策として、関数はタイププレディケートを持つ形で書かれます。例えば、function isCat(obj: Animal): obj is Cat {...}の形式です。ここでの重要なポイントは、関数の返り値の型におけるタイププレディケートです。
コード例:
interface Dog { bark: () => void; } interface Cat { meow: () => void; } type Pet = Dog | Cat; function isDog(pet: Pet): pet is Dog { return (pet as Dog).bark !== undefined; } function makeSound(pet: Pet) { if (isDog(pet)) { pet.bark(); } else { pet.meow(); } }
主な特徴:
タイプガード関数でtrue/falseを返すだけでコンパイラが型を絞り込むのに十分ですか?
いいえ。返り値の型をtype predicate(例えば、pet is Dog)の形式で明示的に指定することが重要です。そうでないと、TypeScriptは自動的に値の型を絞り込みません。関数がtrueまたはfalseのみを返してもです。
コールバック内でタイプガード(例えば、filter内)を使用できますか?その場合、絞り込みは正しく機能しますか?
はい。タイプガードが正しくアノテートされていれば、コンパイラはfilterの後で配列の要素の型を絞り込むことができます。forEach/callback関数内でも同様です。ただし、アノテーションが欠如または誤っている場合、結果は特定の型ではなくユニオンの型になります。
const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // TypeScriptはdogsの型がDog[]であることを知っています
ユーザー定義タイプガードは、typeofやinstanceofを用いた通常の型チェックとは異なりますか?
タイプガード関数は任意の構造のチェックを実装し、複雑さのレベルを問わずチェックを記述し、クラスのないインターフェースを操作できるため、基本の型やクラスだけでなく、より柔軟です。
関数がユーザーをフィルタリングし、タイププレディケートなしでタイプガードを作成します:
function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]
メリット:
デメリット:
正しいタイププレディケートでフィルタリングする関数:
interface Admin { name: string; isAdmin: true; } function isAdmin(user: any): user is Admin { return user && user.isAdmin === true; } const admins = users.filter(isAdmin); // admins: Admin[]
メリット:
デメリット: