型ガードとは、TypeScriptが理解できる実行時のチェックです。チェックしたブランチの中で型が絞り込まれます。typeof、instanceof、in は組み込みの型ガードです。それ以外のチェックには、戻り値の型が型述語 value is Type である関数を書きます。
isUser は実行時には普通の boolean を返します。戻り値の型 value is User が、true という結果が何を保証するかをコンパイラーに伝えるので、if (isUser(x)) のたびに x が User に絞り込まれます。
組み込みの型ガード
次のチェックは、ヘルパー関数なしで型を絞り込みます:
| 型ガード | 例 | 用途 |
|---|---|---|
typeof | typeof x === "number" | プリミティブと関数 |
instanceof | x instanceof Date | クラスのインスタンス |
in | "email" in x | オブジェクトのユニオン型、unknown のオブジェクトのプロパティ |
Array.isArray | Array.isArray(x) | 配列 |
| 等価比較 | x === null、x.kind === "circle" | null/undefined、リテラルのタグ |
| truthiness | if (x) | null と undefined を取り除く |
どれも普通のJavaScriptとして実行されます。TypeScriptが加えるのは絞り込みです。チェックを読んで、各ブランチの型を調整します。形の一覧は 型の絞り込み のページにあります。自作の型ガードは、1つの式に収まらないチェックや、再利用したいチェックのためのものです。
型述語を書く
型述語は parameterName is Type という形で、戻り値の型の boolean の代わりに書きます。絞り込みは両方向に働きます。true なら Type に絞り込まれ、false ならユニオン型から Type が取り除かれます。
型ガードを filter に渡すと、正しく型付けされた配列が得られます。TypeScript 5.5 以降は単純なアロー関数からも型述語が推論されるので、名前付きの型ガードがなくても pets.filter((p) => p.kind === "cat") は Cat[] を返します。
型述語の型は引数の型に収まっている必要があります。function f(x: string): x is number はエラー TS2677 A type predicate's type must be assignable to its parameter's type. です。
コンパイラーは型ガードを信用する
TypeScriptは型ガードが boolean を返すことはチェックしますが、その boolean が正しいかはチェックしません。間違った値に true を返す型ガードは型に嘘をつかせ、コンパイルエラーなしに実行時にプログラムが失敗します。
data.price.toFixed(2) は実行時に TypeError: Cannot read properties of undefined (reading 'toFixed') を投げます。型ガードがそう言ったので、コンパイラーは data.price を number として受け入れました。残りのコードが頼るプロパティはすべてチェックし、型ガードは小さく、テストされた状態で、表す型の近くに置きましょう。
オブジェクトがある型かどうかをチェックする
自作の型ガードの多くはこの疑問から生まれます。データが unknown として(JSON.parse、fetch、localStorage、メッセージから)届き、それがインターフェースに一致するかを知る必要がある場合です。手順は次のとおりです:
typeof value === "object" && value !== null(nullではないオブジェクト)。- 必須プロパティごとに
"prop" in value。unknownに対してinを使うと、そのプロパティがunknown型として型に追加されます。 - 各プロパティの型について
typeof value.prop === "..."(またはネストした型ガード)。 - 配列には
Array.isArray(value.items) && value.items.every(isItem)。
大きな形や深くネストした形では、手書きは面倒になります。Zod や Valibot のようなスキーマライブラリを使えば、形を一度記述するだけで、実行時のチェックとTypeScriptの型の両方が得られます。
アサーション関数: asserts value is Type
アサーション関数は、チェックに失敗すると例外を投げ、成功すれば普通に戻ります。戻り値の型は asserts value is Type(または asserts condition)で、呼び出しのあとのすべてが if なしで絞り込まれます。
つまずきやすいルールが1つあります。アサーション関数は、明示的な型を持つ名前を通して呼び出す必要があります。型注釈のない const のアロー関数 const check = (v: unknown): asserts v is string => {...} は、呼び出し箇所でエラー TS2775 Assertions require every name in the call target to be declared with an explicit type annotation. になります。function 宣言を使うか、定数に関数型の注釈を付けましょう。
型ガード、アサーション関数、キャストの比較
| 方法 | 実行時のチェック | 絞り込む範囲 | 失敗したとき |
|---|---|---|---|
組み込みの型ガード(typeof、in など) | あり | ブランチの中 | 別のブランチに進む |
value is T の関数 | あり(自分のコード) | ブランチの中 | 別のブランチに進む |
asserts value is T の関数 | あり(自分のコード) | 呼び出しのあと | 例外を投げる |
value as T | なし | その式 | 何も起きず、間違った型が広がる |
型アサーション(as)は何もチェックせずに型を変えます。外部からデータが入ってくる境界では、型ガードやアサーション関数が同じ考え方の安全な版です。
クラスでの this を使った型ガード
メソッドは this is Type で、呼び出し元のオブジェクトを絞り込めます。クラスの階層で便利です:
class FileNode {
constructor(public name: string) {}
isDirectory(): this is DirectoryNode {
return this instanceof DirectoryNode;
}
}
class DirectoryNode extends FileNode {
children: FileNode[] = [];
}
function count(node: FileNode): number {
return node.isDirectory() ? node.children.length : 0; // node: DirectoryNode in the true branch
}
よくある質問
TypeScriptの型ガードとは何ですか?
TypeScriptが型の絞り込みに使う、あらゆる実行時のチェックのことです。typeof x === "string"、x instanceof Date、"id" in x、Array.isArray(x)、そして戻り値の型が x is User のような型述語である関数の呼び出しです。チェックしたブランチの中では、変数はより狭い型になります。
TypeScriptでオブジェクトがある型かどうかをチェックするには?
型は実行時に存在しないので、プロパティをチェックします。isUser(value: unknown): value is User という関数を書き、typeof value === "object"、value !== null、そして必須プロパティをそれぞれ in と typeof でテストします。if (isUser(x)) のあとでは x は User 型です。クラスなら x instanceof MyClass で十分です。
TypeScriptの「value is Type」はどういう意味ですか?
型述語で、関数の戻り値の型として使います。関数は実行時には相変わらず boolean を返しますが、true を返すと呼び出し側で引数が Type に絞り込まれ、false を返すとユニオン型の残りのメンバーに絞り込まれます。コンパイラーは関数の本体を検証しないので、チェックは正しく書く必要があります。
型ガードとアサーション関数の違いは何ですか?
型ガード(x is T)は boolean を返し、if の中で絞り込みます。アサーション関数(asserts x is T)は何も返さず、チェックに失敗すると例外を投げるので、if なしで呼び出し以降のすべてが絞り込まれます。分岐には型ガードを、「これが成り立たなければ止める」にはアサーション関数を使います。
TypeScriptでオブジェクトがインターフェースを実装しているかチェックできますか?
直接はできません。インターフェースは消去され、instanceof も受け付けません。インターフェースのプロパティをチェックする型ガードを書くか、リテラルのタグプロパティ(kind: "user")を追加して比較します。Zod のようなスキーマライブラリなら、1つの定義からチェックと型の両方を生成できます。