Menu

TypeScriptの型ガード: オブジェクトの型をチェックする

型ガードとは、TypeScriptが理解できる実行時のチェックです。組み込みの型ガード、value is Type という型述語を使った自作の型ガード、オブジェクトがある型かどうかのチェック、asserts を使ったアサーション関数、unknown のデータの検証方法を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

型ガードとは、TypeScriptが理解できる実行時のチェックです。チェックしたブランチの中で型が絞り込まれます。typeof、instanceof、in は組み込みの型ガードです。それ以外のチェックには、戻り値の型が型述語 value is Type である関数を書きます。

isUser は実行時には普通の boolean を返します。戻り値の型 value is User が、true という結果が何を保証するかをコンパイラーに伝えるので、if (isUser(x)) のたびに x が User に絞り込まれます。

組み込みの型ガード

次のチェックは、ヘルパー関数なしで型を絞り込みます:

型ガード例用途
typeoftypeof x === "number"プリミティブと関数
instanceofx instanceof Dateクラスのインスタンス
in"email" in xオブジェクトのユニオン型、unknown のオブジェクトのプロパティ
Array.isArrayArray.isArray(x)配列
等価比較x === null、x.kind === "circle"null/undefined、リテラルのタグ
truthinessif (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、メッセージから)届き、それがインターフェースに一致するかを知る必要がある場合です。手順は次のとおりです:

  1. typeof value === "object" && value !== null(null ではないオブジェクト)。
  2. 必須プロパティごとに "prop" in value。unknown に対して in を使うと、そのプロパティが unknown 型として型に追加されます。
  3. 各プロパティの型について typeof value.prop === "..."(またはネストした型ガード)。
  4. 配列には 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つの定義からチェックと型の両方を生成できます。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める