【TypeScript】型チェックの全手段|typeof・型ガード保存版
TypeScriptで「この値の型を確かめて安全に扱いたい」と思ったとき、型チェックの手段が多く、どれを使えばいいか迷うことがありませんか?
typeof null が "object" になるって聞いたけど、じゃあnullチェックはどう書けばいい?
as で型を指定したのに実行時に落ちた…型チェックしてるつもりだったんだけど?
型チェックの手段がバラバラに見えるのは、それぞれが「どこで・何を」判定しているのかが整理されていないからです。この記事では、typeof・instanceof・in・ユーザー定義型ガード・null判定・asといった手段をまとめて見渡し、目の前の値に対して適切な判定方法を選べるように整理します。
この記事は次のような方におすすめです。
- typeof・instanceof・inなどの判定手段を、いつ使い分ければいいか整理したい方
typeof nullの罠やinstanceofの限界でつまずいた経験がある方asを型チェックのつもりで使っていて、実行時エラーに不安がある方- 外部から来るデータ(API・JSON)を安全に扱う判断基準がほしい方
読み終える頃には、型チェックを「静的チェック・実行時判定・検査を迂回する指定」の3層で捉える視点が身につき、比較表と判断フローを実装時の指針として使えるようになります。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
エンジニア転職体験談|4社で年収250→510万にした全記録
型チェックには2種類ある|静的チェックと実行時判定
TypeScriptの型チェックは、働くタイミングが違う2つの層に分かれます。ひとつはコンパイル時にtscが行う静的な型検査、もうひとつは実行時にコードが値そのものを確かめる動的な判定です。この2つを混同すると、「型注釈を書いたのに実行時に落ちる」「typeofで判定したのにコンパイルエラーが消えない」といったズレが起きます。
静的チェックは、型注釈と型推論で決まった型どうしの整合性をtscが検査するものです。実行時には何も残りません。次のように型が合わない代入は、コンパイル時点でエラーになります。
let count: number = 0;
count = "10"; // 例:TS2322 Type 'string' is not assignable to type 'number'.(TypeScriptバージョンにより文言が異なる場合あり)
一方、実行時判定はtypeofやinstanceofのように、実際に動いている値を見て分岐する仕組みです。JavaScriptとしてそのまま実行され、条件によって型を絞り込みます。両者の違いを整理すると次のとおりです。
| 層 | 働くタイミング | 判定の主体 | 代表例 | 実行時に残るか |
|---|---|---|---|---|
| 静的チェック | コンパイル時 | tsc(型注釈・型推論) | 型注釈の不一致検出 | 残らない |
| 実行時判定 | 実行時 | コード自身 | typeof・instanceof・in | 残る |
型チェックを語るときは、まず「静的か実行時か」を切り分けるのが出発点です。型推論はこの静的な層を支える土台です。
型推論と型注釈は静的チェックの前提
静的な型チェックが成立するのは、型注釈または型推論によって「この変数はこの型だ」という前提が先に決まっているからです。値を絞り込む操作(narrowing)は、その型を出発点にして、より具体的な型へ狭めていく行為です。土台となる型がなければ、絞り込む対象そのものが存在しません。
たとえばletで初期化した変数は、代入値から型が推論されます。明示的に注釈を付けた場合と比べてみます。
let x = "a"; // 推論結果は string("a" というリテラル型には固定されない)
let y: "a" = "a"; // 注釈により "a" というリテラル型に固定される
x = "b"; // OK:string なので別の文字列を入れられる
y = "b"; // 例:TS2322 Type '"b"' is not assignable to type '"a"'.(TypeScriptバージョンにより文言が異なる場合あり)
xにカーソルを合わせるとstring、yは"a"とホバー表示され、推論と注釈で決まる型が変わることを確認できます。この前提となる型があるからこそ、後続の判定で型を安全に狭められるわけです。型推論の細かな規則は多岐にわたりますが、型判定では「静的な型が判定の前提になる」という関係が重要です。
実行時に型を判定する4つの基本手段
ここからは、実行時に値の型を確かめる代表的な手段として、typeof・instanceof・in・ユーザー定義型ガードを整理します。それぞれ判定できる対象が異なり、向いている場面も変わります。
全体を早見表にすると次のようになります。
| 手段 | 判定できる対象 | 静的/動的 | 代表構文 | 向く場面 |
|---|---|---|---|---|
| typeof | プリミティブ(文字列・数値・真偽値など) | 動的 | typeof x === "string" |
基本型の絞り込み |
| instanceof | クラスのインスタンス | 動的 | x instanceof Date |
クラス由来のオブジェクト |
| in | プロパティの有無 | 動的 | "id" in x |
ユニオン型オブジェクトの分岐 |
| ユーザー定義型ガード | 任意の複雑な構造 | 動的 | function isFoo(v): v is Foo |
上記で判定しきれない構造 |
この表の要点は、判定したい対象がプリミティブなのか、クラスなのか、構造なのかで選ぶ手段が変わるということです。
typeofでプリミティブを判定する(落とし穴つき)
typeofは文字列・数値・真偽値といったプリミティブの判定に向いた手段です。返す文字列は"string"・"number"・"boolean"・"undefined"・"object"・"function"などで、条件分岐に使うとその分岐内で型が絞り込まれます。
function format(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase(); // ここでは value は string に絞り込まれる
}
return value.toFixed(2); // ここでは value は number
}
一方で、typeof null は "object" を返すという有名な落とし穴があります。オブジェクトかどうかをtypeof x === "object"だけで判定すると、nullもすり抜けてしまいます。
const x: unknown = null;
console.log(typeof x); // "object" ← null なのに "object" と出る
そのため、オブジェクトを判定したいときはnullを先に弾く必要があります。
function isNonNullObject(x: unknown): boolean {
return typeof x === "object" && x !== null;
}
このように、typeofはプリミティブには強い一方で、nullやオブジェクトを厳密に判定するには条件を足す必要がある手段です。まずは「typeofだけではnullを除外できない」と覚えておくとよいですよ。
instanceofでクラスインスタンスを判定する
instanceofは、ある値が特定のクラス(コンストラクタ)から作られたインスタンスかどうかを判定する手段です。プロトタイプチェーンをたどって確認するため、DateやError、自作クラスのインスタンスに対して有効です。
function describe(value: Date | string) {
if (value instanceof Date) {
return value.toISOString(); // value は Date に絞り込まれる
}
return value.trim(); // value は string
}
注意したいのは、interfaceやtype(型エイリアス)はコンパイル後に消えるため、instanceofの対象にできない点です。interfaceは実行時に存在する値ではなく静的な型情報なので、instanceofの右辺に置こうとするとエラーになります。
interface User {
name: string;
}
const u: unknown = { name: "a" };
if (u instanceof User) { // 例:TS2693 'User' only refers to a type, but is being used as a value here.(TypeScriptバージョンにより文言が異なる場合あり)
console.log(u.name);
}
instanceofが使えるのは実行時に存在するクラスに対してだけで、interfaceやtypeの構造を判定したいときは別の手段が必要になります。
in演算子でプロパティの有無から絞り込む
in演算子は、あるオブジェクト自身またはプロトタイプチェーン上に特定のプロパティがあるかを調べ、その有無で型を絞り込む手段です。とくに、形の異なる複数のオブジェクトを含むユニオン型を分岐させるときに向いています。
type Dog = { bark: () => void };
type Cat = { meow: () => void };
function speak(animal: Dog | Cat) {
if ("bark" in animal) {
animal.bark(); // animal は Dog に絞り込まれる
} else {
animal.meow(); // animal は Cat
}
}
"bark" in animalが真になった分岐ではanimalがDogにホバー表示され、barkプロパティへ安全にアクセスできます。判定の手がかりが「型の名前」ではなく「プロパティの有無」であるとき、inはわかりやすい絞り込み手段になります。
複雑な構造は専用関数で絞り込む
typeof・instanceof・inで判定しきれない複雑な構造は、戻り値を型述語にした専用関数(ユーザー定義型ガード)で絞り込みます。関数の戻り値の型を引数 is 型という形で書くと、呼び出し結果がtrueになった分岐で、引数の型が指定した型に絞り込まれます。
型述語を付けない普通のboolean関数では、呼び出したあとも型は狭まりません。
type User = { id: number; name: string };
function isUserBad(v: unknown): boolean {
return typeof v === "object" && v !== null && "id" in v;
}
function useValue(v: unknown) {
if (isUserBad(v)) {
v.name; // 例:TS18046 'v' is of type 'unknown'.(TypeScriptバージョンにより文言が異なる場合あり)
}
}
戻り値をv is Userに変えると、呼び出し結果がtrueになった分岐でvがUserとして扱われ、プロパティへ安全にアクセスできます。
function isUser(v: unknown): v is User {
const obj = v as { id?: unknown; name?: unknown };
return typeof v === "object" && v !== null && typeof obj.id === "number" && typeof obj.name === "string";
}
function useValue(v: unknown) {
if (isUser(v)) {
console.log(v.name); // v は User に絞り込まれ、アクセスできる
}
}
ここで大切なのは、asで無理やりUserとして扱うのではなく、型述語で「実際に構造を確かめてから絞り込む」ことです。asは実行時に何も確認しないため、値が想定と違っても素通りしてしまいます。型述語なら、条件式で本当にその構造かを検査したうえで型が狭まるので、実行時の値と静的な型がずれにくくなります。
nullとundefinedのチェックは別枠で押さえる
nullとundefinedは、他の型判定とは分けて考える必要があります。strictNullChecksが有効なとき、nullやundefinedを含みうる型では、それらを除外しないとプロパティへアクセスできないからです。これは実行時判定というより、静的チェックの段階でtscが指摘してくるポイントです。
function getLength(obj: { name: string } | null) {
return obj.name.length; // 例:TS2531 Object is possibly 'null'.(TypeScriptバージョンにより文言が異なる場合あり)
}
このエラーは、アクセス前にnullを絞り込むことで解消します。早期returnやオプショナルチェイニング(?.)を使うのが定番の形です。
function getLength(obj: { name: string } | null) {
if (obj === null) return 0; // ここで null を外す
return obj.name.length; // obj は { name: string } に絞り込まれる
}
nullとundefinedは「値が無いかもしれない」という共通の性質を持つ一方で、扱い方には細かな違いがあります。非nullアサーションやオプショナル関連の記法も関わるため、まとめて押さえておくと実装で迷いにくくなります。
asは型チェックとは別物として扱う
as(型アサーション)は、型チェックの手段ではありません。asがするのは、コンパイラが持っている型を書き手の指定で上書きすることだけで、実行時には一切検査しません。つまり、値が実際にはその型でなくても、コンパイルエラーを黙らせてしまいます。
const data: unknown = { id: "not-a-number" };
const user = data as { id: number };
console.log(user.id * 2); // 型上は number だが実行時は "not-a-number" → 後続処理の不具合につながる
このコードは静的には問題なく通りますが、idは実際には文字列なので、計算結果はNaNになり、後続の処理が壊れる可能性があります。asは誤りを消すのではなく、実行時まで隠すだけです。だからこそ、外部から来るデータの検証にasを使うのは危険で、型ガードやスキーマ検証で「本当にその型か」を確かめるべきです。
satisfies演算子とasの違いを知りたい方はこちらへ。
as constでリテラル型を保つ書き方はこちらで整理しています。
どの型チェックを選ぶか(判断フローと実務の指針)
目の前の値に対してどの手段を選ぶかは、値の性質を上から順に当てはめていくと判断しやすくなります。判断フローは次のとおりです。
- プリミティブ(文字列・数値・真偽値)か?
typeof - クラスのインスタンスか?
instanceof - 特定プロパティの有無で見分けられるか?
in - それ以外の複雑な構造か?
ユーザー定義型ガード(v is 型) - 外部から来る未知データ(API・JSON)か?
スキーマ検証(zod など)
実務で効いてくるのは、「入力の境界で検証し、内側では型を信頼する」という考え方です。アプリの外から入ってくるデータは何が来るか保証がないので、受け取る境界で徹底的に検証します。いったん検証を通ったあとの内側のコードでは、確定した型を信頼してそのまま扱えば、判定をあちこちに散らさずに済みます。
外部データの検証は、手書きの型ガードよりもスキーマ検証ライブラリに任せると、検証ルールと型を一元管理しやすくなります。エラーを戻り値として型安全に扱う設計もあわせて検討すると、境界の堅牢さが増します。
【付録】さらに学びを深めるためのリソース
さらに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プログラマーになれるはずです。
まとめ – 型チェックは3層で捉えると迷わない
この記事では、TypeScriptの型チェックの全手段を整理しました。
- 型チェックは静的チェック(tsc・型注釈/推論)/実行時判定(typeof・instanceof・in・ユーザー定義型ガード)/検査を迂回する指定(as)の3層で捉えると混同しない
- 実行時判定は、プリミティブなら
typeof、クラスならinstanceof、プロパティの有無ならin、複雑な構造ならユーザー定義型ガードと使い分ける typeof nullが"object"になる、instanceofはinterfaceに使えないなど、各手段には条件と例外があるasは実行時に何も検査せず誤りを隠すため、型チェックの代わりにはならない- 外部から来るデータは入力の境界で検証し、内側では確定した型を信頼する
手段そのものより先に「静的チェックか・実行時判定か・検査を迂回する指定か」を切り分けることで、目の前の値に対して適切な判定を選びやすくなります。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






