TypeScript で string | number のような「複数の型のどれか」を表すユニオン型を扱っていると、「文字列のときだけ toUpperCase() を呼びたいのに、型エラーになる」という場面に必ず出会います。これは、その値がいまどちらの型なのかを TypeScript がまだ判断できていないためです。この「いまはこの型だ」とコンパイラに伝えて型を狭めることを型の絞り込み(ナローイング)と呼び、そのために使う仕組みが型ガードです。この記事では、typeof・instanceof・in 演算子・truthy 判定といった標準の型ガードから、is を使った自作の型ガード関数までを、初心者にも分かりやすく解説します。
目次
なぜ型ガードが必要なのか
まず、型ガードが無いとどうなるかを見てみましょう。次の関数は引数が string | number、つまり文字列か数値のどちらかです。文字列なら大文字に、数値なら小数点2桁にしたいのですが、そのまま書くとエラーになります。
function format(value: string | number) {
// エラー: 'string | number' に toUpperCase は存在しない
// return value.toUpperCase();
return value;
}
value は文字列かもしれないし数値かもしれないため、TypeScript は「どちらでも安全に使えるメンバー」しか許可しません。toUpperCase() は文字列にしかないので、数値だった場合に実行時エラーになる可能性があり、コンパイル段階で止められます。この状態を解決するのが型ガードです。「この if の中では文字列である」とコンパイラに分からせれば、その中では文字列のメソッドを安全に使えるようになります。
typeof で基本型を絞り込む
もっとも基本的な型ガードが typeof です。JavaScript の typeof 演算子は値の種類を文字列で返しますが、TypeScript はこの結果を見て「この分岐の中の型」を自動的に絞り込んでくれます。'string'・'number'・'boolean'・'symbol'・'undefined'・'function'・'object' の判定に使えます。
function format(value: string | number): string {
if (typeof value === 'string') {
// この中では value は string に絞り込まれる
return value.toUpperCase();
}
// ここに来た時点で value は number
return value.toFixed(2);
}
console.log(format('hello')); // 'HELLO'
console.log(format(3.14159)); // '3.14'
if (typeof value === 'string') の中では value の型が string に狭まり、toUpperCase() が使えます。さらに注目したいのは、if を抜けたあとの部分です。ユニオン型が string | number の2択なので、「文字列でなければ数値」と TypeScript が推論し、return value.toFixed(2) の行では value が自動的に number になっています。このように、片方を return で処理すると残りが自然に絞り込まれる書き方は頻出のパターンです。
instanceof でクラスのインスタンスを判定する
クラスから作られたオブジェクト(インスタンス)を判定したいときは instanceof を使います。typeof は基本型の判定に使うのに対し、instanceof は「この値は特定のクラスから作られたものか」を調べます。Date や Error のような組み込みクラス、自分で定義したクラスのどちらにも使えます。
function toISO(value: Date | string): string {
if (value instanceof Date) {
// value は Date に絞り込まれる
return value.toISOString();
}
// ここでは value は string
return value;
}
console.log(toISO(new Date('2026-07-16'))); // '2026-07-16T00:00:00.000Z'
console.log(toISO('2026-07-16')); // '2026-07-16'
value instanceof Date が真になる分岐では、value は Date 型として扱われ、toISOString() を安全に呼べます。catch でエラーを受け取ったとき、それが Error のインスタンスかを err instanceof Error で確かめてから err.message を読む、といった使い方もよくあります。
in 演算子でプロパティの有無を調べる
オブジェクトどうしのユニオン型では、typeof や instanceof では区別できないことがあります。そんなときは in 演算子で「特定のプロパティを持っているか」を調べ、それによって型を絞り込みます。'プロパティ名' in オブジェクト という形で書きます。
type Dog = { name: string; bark: () => void };
type Cat = { name: string; meow: () => void };
function speak(animal: Dog | Cat): void {
if ('bark' in animal) {
// bark を持つので animal は Dog
animal.bark();
} else {
// それ以外は Cat
animal.meow();
}
}
'bark' in animal が真なら、そのプロパティを持つのは Dog だけなので animal は Dog に絞り込まれます。Dog と Cat のように、持っているメソッドやプロパティが違うオブジェクトを見分けたいときに便利です。ここまでに登場した3つの型ガードは、対象によって使い分けます。次の表で整理しておきましょう。
| 型ガード | 判定できるもの |
|---|---|
typeof x === '...' | string・number・boolean などの基本型 |
x instanceof クラス | Date・Error や自作クラスのインスタンス |
'プロパティ' in x | オブジェクトが特定のプロパティを持つか |
truthy 判定で null・undefined を取り除く
string | null や string | undefined のように「値があるか無いか」を含む型では、単純な if による truthy 判定(値が「真」とみなせるかの判定)で null や undefined を取り除けます。これも立派な型ガードで、絞り込みが働きます。
function greet(name: string | null): string {
if (name) {
// null が取り除かれ、name は string
return `こんにちは、${name}さん`;
}
return 'こんにちは、ゲストさん';
}
console.log(greet('佐藤')); // 'こんにちは、佐藤さん'
console.log(greet(null)); // 'こんにちは、ゲストさん'
if (name) の中では null が除かれ、name は string になります。ただし注意したいのは、空文字 '' や数値の 0 も truthy 判定では「偽」とみなされる点です。「値が存在するか」だけを厳密に見たいときは、if (name !== null && name !== undefined) や if (name != null) のように明示的に比較するほうが安全です。
is を使ったユーザー定義型ガード
判定のロジックが複雑になったり、同じ判定を何度も使ったりする場合は、型ガードを関数として切り出せます。このとき戻り値の型を boolean ではなく 引数 is 型 と書くのがポイントです。これをユーザー定義型ガード(型述語)と呼び、「この関数が true を返したら、引数はこの型だ」とコンパイラに教えられます。
type Fish = { swim: () => void };
type Bird = { fly: () => void };
// 戻り値の型が 'pet is Fish'(型述語)になっている
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird): void {
if (isFish(pet)) {
// isFish が true なので pet は Fish に絞り込まれる
pet.swim();
} else {
pet.fly();
}
}
isFish の戻り値を pet is Fish と書いたことで、if (isFish(pet)) の中では pet が Fish に絞り込まれ、swim() を安全に呼べます。もし戻り値の型を単なる boolean にしてしまうと、関数が true を返しても TypeScript は型を狭めてくれません。判定を関数に分けるときは、必ず is による型述語を書くのが安全です。
絞り込みがうまく効かないとき
判定結果を変数に入れて後から使っている
TypeScript の絞り込みは、判定した「その変数」に対してのみ効きます。たとえば const isStr = typeof value === 'string' のように結果を別の変数へ入れ、あとで if (isStr) と書いても、value の型は狭まりません。判定は使う場所で直接 if (typeof value === 'string') と書くか、前述のユーザー定義型ガードとして切り出しましょう。
コールバックの中で絞り込みが失われる
絞り込んだ直後にコールバック関数(setTimeout や配列の forEach など)を挟むと、その中では「途中で値が変わったかもしれない」と判断され、絞り込みが解除されることがあります。これは変数が let で再代入されうる場合に起きやすい挙動です。再代入しない値なら const を使う、必要な値をコールバックの外でローカル変数に取り出しておく、といった対処で回避できます。
まとめ
型ガードは、ユニオン型のように複数の可能性を持つ値を、その場に応じた具体的な型へ絞り込む仕組みです。基本型なら typeof、クラスのインスタンスなら instanceof、オブジェクトのプロパティの有無なら in 演算子を使い、null や undefined は truthy 判定や明示的な比較で取り除けます。判定が複雑になる場合は、戻り値を 引数 is 型 としたユーザー定義型ガードに切り出すと、絞り込みを再利用できます。型ガードを使いこなすと、危険なキャスト(as)に頼らず、コンパイラの後押しを受けながら安全にコードが書けるようになります。