TypeScriptの型ガードを使ってunionやunknownの値を絞り込もうとしても、「プロパティが存在しない」と赤線が消えず、思うように型が狭まらない場面は多いものです。

string | number の値に toFixed したいだけなのに、なんでエラーが消えない?
typeof と instanceof と in、結局どれをいつ使えばいいのか分からない…
自分で書いた is の関数、なんだか実行時に壊れる気がして怖いんだけど。

型ガードは、if などの条件分岐の中で「この値はこの型だ」とTypeScriptに教え、安全にプロパティやメソッドへアクセスできるようにする仕組みです。この記事では、組み込みの3種(typeof・instanceof・in)とユーザー定義型ガード(is)を実コードと出力で確認し、最後に対象別の判断フローへ落とし込みます。読み終えるころには、絞り込めずに詰まっていた値を自信を持って扱えるようになり、自作型ガードの危うさも見抜けるようになります。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • union や unknown の値でプロパティアクセスがエラーになり困っている方
  • typeof・instanceof・in・is の使い分けに自信が持てない方
  • 外部APIやフォーム入力のデータを安全に検証したい中級者の方
  • 自作した型ガードが実行時に壊れる原因を知りたい方

判定対象に応じて適切な型ガードを選べるようになり、絞り込み後のコードがコンパイルエラーなく通る状態を作れます。

それでは、順を追って詳しく見ていきましょう!

型ガードとは?なぜ必要か

型ガードは、実行時の条件で値の型を判定し、その分岐の内側だけ型を狭める(絞り込む)仕組みです。union や unknown のように複数の可能性を持つ型は、そのままではすべての型に共通する操作しか許されません。

たとえば string | number の値に、数値専用のメソッドへ直接アクセスすると失敗します。

function format(value: string | number) {
  value.toFixed(2); // 例:TS2339 Property 'toFixed' does not exist on type 'string | number'.
}

toFixed は number にしかないため、string の可能性が残る value では呼べません。ここで typeof を使って分岐すると、TypeScriptは制御フロー分析(control flow analysis)によって、ブロック内では型が絞られたものとして扱います。

function format(value: string | number) {
  if (typeof value === "number") {
    return value.toFixed(2); // ここでは value は number
  }
  return value.toUpperCase(); // ここでは value は string
}

if の中で value が number に絞られ、toFixed が通ります。else 側に回った value は string と判断され、toUpperCase も問題なく呼べます。型ガードのねらいは「広い型を、安全に触れる狭い型へ変えること」にあります。

unknown 型の値も、何も操作できない状態から型ガードで段階的に絞ることで、はじめて安全に扱えます。

typeofによる型ガード

typeof で判定できるのは主にプリミティブ型で、"string" "number" "boolean" "undefined" "symbol" "bigint"、そして関数を表す "function"、それ以外のオブジェクトを表す "object" のいずれかが返ります。

function describe(x: string | number | (() => void) | undefined) {
  if (typeof x === "string") return `文字列: ${x.length}文字`;
  if (typeof x === "number") return `数値: ${x.toFixed(1)}`;
  if (typeof x === "function") return `関数: ${x.name}`;
  return "未定義";
}

それぞれの分岐内で x が対応する型に絞られ、lengthtoFixedname といった型固有のプロパティへ安全にアクセスできます。一方で、typeof は具体的なクラスやオブジェクトの中身までは区別できません。配列もクラスインスタンスも、まとめて "object" を返すだけです。オブジェクトの判別には in や instanceof を使います。

typeof null が object になる罠

typeof null は “null” ではなく “object” を返します。これはJavaScript初期からの仕様で、null を弾くつもりで typeof x === "object" と書くと、null がそのまますり抜けてしまいます。

console.log(typeof null); // "object"

function pick(x: object | null) {
  if (typeof x === "object") {
    // x はまだ object | null。null が混ざっている
    return x; // 例:null も到達しうる
  }
}

null を確実に弾くなら、typeof に頼らず x !== null を先に書くか、x != null(null と undefined の両方を除外)を使うのが安全です。

function pick(x: object | null) {
  if (x !== null) {
    return x; // ここで x は object に絞られる
  }
  return "なし";
}

instanceofによる型ガード

instanceof は、値のプロトタイプチェーンに指定したコンストラクタの prototype が含まれるかで判定します。Date や Error のように new で生成したクラスインスタンスの判別に向きます。

function handle(x: Date | Error) {
  if (x instanceof Date) {
    return x.toISOString(); // x は Date
  }
  return x.message; // x は Error
}

注意したいのは、プレーンオブジェクトや配列の判定です。配列は x instanceof Array で判定できる場合もありますが、配列かどうかを見たいときは Array.isArray を使うほうが意図が明確で、別の実行コンテキスト(iframe など、いわゆるクロスリアルム)をまたいでも正しく動きます。

function size(x: unknown) {
  if (Array.isArray(x)) {
    return x.length; // x は any[] として配列扱い
  }
  return 0;
}

instanceof はあくまでプロトタイプチェーンを前提とするため、Object.create(null) で作った値や、ただのオブジェクトリテラルの“形”を判定する用途には向きません。特定プロパティの有無を見るなら in、配列なら Array.isArray を使います。クラスインスタンスかどうかが軸のときに instanceof、配列かどうかなら Array.isArray と覚えておくと迷いにくくなります。

in演算子による型ガード

in 演算子は「あるプロパティがオブジェクトに存在するか」を見て、union を判別します。判別したい型に固有のプロパティがあるとき、その有無で分岐できます。

type Cat = { meow: () => void };
type Dog = { bark: () => void };

function speak(animal: Cat | Dog) {
  if ("meow" in animal) {
    animal.meow(); // animal は Cat
  } else {
    animal.bark(); // animal は Dog
  }
}

"meow" in animal が真なら Cat、偽なら Dog に絞られます。共通の判別用プロパティ(たとえば kind: "cat" | "dog" のようなリテラル)を持たせた判別可能ユニオンにしておくと、in よりもさらに素直に分岐できます。

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape) {
  if (s.kind === "circle") return Math.PI * s.radius ** 2;
  return s.side ** 2;
}

ただし、in が見ているのはキーの存在だけで、その値が期待した型かまでは保証しません。"id" in obj が真でも、obj.id が文字列か数値かまでは別途確認が必要です。

ユーザー定義型ガード(is演算子)の書き方

組み込みの3種で判別しきれない複雑な型は、戻り値の型に 引数 is 型 という型述語(type predicate)を書いた関数で絞り込めます。この関数が true を返すと、呼び出し側でその引数が指定の型に絞られます

type User = { id: number; name: string };

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof (value as Record<string, unknown>).id === "number" &&
    "name" in value &&
    typeof (value as Record<string, unknown>).name === "string"
  );
}

戻り値型を明示的に : boolean と書いた場合、true を返しても呼び出し側の型ガードにはなりません。TypeScript 5.5以降は単純な条件なら型述語が推論される場合もありますが、意図を明確にするなら value is User と書くのが確実です。

function greet(value: unknown) {
  if (isUser(value)) {
    return `こんにちは、${value.name}さん`; // value は User
  }
  return "不明なユーザー";
}

unknown を引数に取り、内部で十分なチェックを行ってから is を返す形にすれば、外部から来た得体の知れない値も、安全に既知の型へ絞り込めます

なかむぅ
なかむぅ
コンパイラへ一方的に型を宣言する as(型アサーション)と、実行時条件で絞る型ガードの違いを押さえたい方はこちらをどうぞ。
TypeScriptのasとは?型アサーションの危険性と安全な代替TypeScriptのas(型アサーション)を実コードとtscのエラーで解説。書き方・キャストとの違い・なぜ危険かを示し、asを避ける型ガードやsatisfiesまで網羅。エラーを消すだけでなく型安全を守りたい人へ。...

自作型ガードは嘘をつける落とし穴

ユーザー定義型ガードの怖いところは、チェックが不十分でも true を返せてしまう点です。TypeScriptは関数の中身が型述語と整合するかまでは検証しません。

type User = { id: number; name: string };

// name のチェックが漏れている
function isUserLoose(value: any): value is User {
  return typeof value === "object" && value !== null && "id" in value;
}

function run(data: unknown) {
  if (isUserLoose(data)) {
    // 型の上では User だが、name が無いかもしれない
    return data.name.toUpperCase(); // 例:TypeError: Cannot read properties of undefined(要確認)
  }
}

name の検査が抜けているのに value is User を返しているため、{ id: 1 } のような値でも true となり、後段の data.name で実行時エラーになりえます。型述語は「書き手の宣言」であって、TypeScriptが正しさを保証してくれるわけではないのです。手書きのチェックは、対象が複雑になるほど漏れが生じやすくなります。

どの型ガードを使う?選び方の判断フロー

判定したい対象から逆引きすると、選ぶべき型ガードは整理できます。まず対象がプリミティブかオブジェクトかで大きく分かれるのがポイントです。

値の型判定方法を選ぶフローチャート。プリミティブはtypeof、クラスのインスタンスはinstanceof、配列はArray.isArray、オブジェクトの形はin、未知の外部データはユーザー定義型ガードやスキーマ検証を使う流れを示している

4手法の特徴を比較すると次のとおりです。

手法 判定対象 書き方 限界
typeof プリミティブ+関数 typeof x === "number" 具体クラスは区別不可。null も “object”
instanceof クラスインスタンス x instanceof Date プロトタイプチェーン前提。リテラルや別リアルムに弱い
in プロパティの有無 "id" in x 値の型までは保証しない
ユーザー定義(is) 任意の複雑な型 value is User 実装が不十分だと嘘をつける

単純な判定は組み込み3種で十分ですが、外部データのように構造が複雑で信頼できない値は、自作の is か、より堅牢なスキーマ検証へ寄せるのが安全です。

【付録】さらに学びを深めるためのリソース


さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。

おすすめの書籍

ゼロからわかる TypeScript入門


技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。

変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。

プロを目指す人のためのTypeScript入門


技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。

オンラインで参照できる公式ドキュメント

TypeScript公式ハンドブック


https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。

TypeScript Deep Dive


https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。

TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。


まとめ – 対象別に選ぶ型ガードの判断基準

この記事の要点をまとめます。

  • 型ガードは制御フロー上で union や unknown の型を安全な狭い型へ絞り込む仕組み
  • 組み込みの typeof(プリミティブ)/instanceof(クラスインスタンス)/in(プロパティ有無)を対象別に選ぶ
  • typeof null は “object” を返すため、null は別途弾く
  • 複雑・未知のデータはユーザー定義型ガード(is)で絞るが、実装が不十分だと嘘をつけるため検証を怠らない

判定したい対象から逆引きして手法を選び、自作の型ガードには十分なチェックを添える。この基準を持っておけば、絞り込みで詰まる場面はぐっと減らせます。


※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。