やあ。前回のリリースで、また若手が `as any` の魔窟に足を踏み入れて自爆しているのを目撃してね。そろそろ「なんとなく型アサーションで逃げる癖」を直してもらわないと、プロダクトの寿命が縮まる一方だと危機感を抱いているところだ。
今回は、TypeScriptの中級へステップアップする上で避けて通れない、`instanceof` 演算子による型ガード(Type Narrowing)について話をしよう。
公式ドキュメントを読めば「クラスのインスタンスかどうかを判定できる」なんてことは書いてある。だが、「なぜそれがブラウザのランタイムと完全に調和するのか」「実務の複雑なエラーハンドリングやAPIレスポンスの解体でどう活きるのか」というレイヤーまで踏み込んでいる記事は意外と少ない。
シニアの視点から、裏側の仕組みも含めて徹底的に叩き込んでやるから、しっかりついてきてくれ。
—
なぜ `typeof` や `as` では不十分なのか?
実務でコードを書いていると、外部から入ってきた「正体のわからないデータ」を安全に料理しなければならない場面に直面する。特にAPIクライアントのレイヤーや、複雑なドメインモデルを扱うフロントエンドでは日常茶飯事だ。
ここでやりがちなのが、以下のアンチパターンだ。
// ❌ 現場で見かける最悪なアプローチ(絶対真似しちゃダメだぞ)
function handleUser(response: unknown) {
// 強引な型アサーション。これじゃTypeScriptを使っている意味がない
const user = response as UserApiClass;
user.getProfile(); // ランタイムでクラッシュする爆弾
}
`as UserApiClass` は、TypeScriptのコンパイラに対して「黙れ、俺はこの型だと信じているんだ」と目隠しをさせる呪文にすぎない。もしランタイムの `response` がプレーンなJavaScriptのオブジェクトだったらどうなるか? `user.getProfile is not a function` というお馴染みのエラーが、容赦なく本番環境のユーザーの画面を真っ白にする。
そこで登場するのが、型ガードだ。TypeScriptには `typeof` や `in` などいくつかの絞り込み手法があるが、「カスタムクラスのインスタンス」を相手にする場合、`instanceof` の右に出るものはいない。
—
ブラウザの裏側で `instanceof` は何をしているのか?
ここで少し、JavaScriptエンジンの裏側の話をしておこう。実務でハイパフォーマンスなコードを書くためには、自分が使っている道具が内部でどう動いているかを知る必要がある。
`a instanceof B` という式を書いたとき、ブラウザ(V8など)は内部で何をやっていると思う?
答えは単純で、プロトタイプチェーンの走査だ。
1. `B` が持つ `prototype` プロパティの参照を取得する。
2. `a` の内部プロトタイプ(__proto__、現代の仕様では `Object.getPrototypeOf(a)`)を辿っていく。
3. 途中で `B.prototype` と一致するものが見つければ `true`、原型を辿り切って `null` に達しても見つからなければ `false` を返す。
つまり、`instanceof` は単なる見た目の判定ではなく、オブジェクトの出自(血統)をランタイムで厳密に追跡する強力なメカニズムなのだ。TypeScriptはこのランタイムの挙動(JavaScriptのセマンティクス)を完全に理解しており、`if (x instanceof Class)` のブロックに入った瞬間、コンパイラは `x` の型を自動的にそのクラス型へと絞り込む(Narrowing)。これが、型安全性と実行時安全性のマリアージュというわけだ。
—
現場で使える実践パターン:エラーハンドリングの例
百聞は一見にしかず。実務でよくある「独自エラークラスのハンドリング」を題材に、綺麗なコードを見てみよう。
以下のコードをそのまま君のエディタに貼り付けてみてほしい。
/
- 1. ドメイン固有のカスタムエラークラス群を定義する
/
class NetworkError extends Error {
constructor(message: string, public statusCode: number) {
super(message);
this.name = “NetworkError”;
}
// ネットワークエラー特有のリカバリ判定メソッド
public isRetryable(): boolean {
return this.statusCode >= 500;
}
}
class ValidationError extends Error {
constructor(message: string, public fields: Record
super(message);
this.name = “ValidationError”;
}
}
/
- 2. APIを叩く非同期関数(何らかのエラーを投げる想定)
/
async function fetchUserData(userId: string): Promise
// ダミーの処理:実際はfetchなどが入る
if (!userId) {
throw new ValidationError(“ユーザーIDが不正です”, { userId: “必須項目です” });
}
throw new NetworkError(“サーバーが応答しません”, 503);
}
/
- 3. エラーをハンドリングするメイン関数
- ここで instanceof 演算子による型ガードが火を吹く!
/
async function handleApiCall(userId: string) {
try {
await fetchUserData(userId);
} catch (error: unknown) {
// catch句の変数(error)はデフォルトで unknown 型になる(TypeScript 4.0以降)
// ここで instanceof を使って絞り込みを行う
if (error instanceof NetworkError) {
// ✅ このブロックの中では、error は NetworkError 型として扱われる
console.error(`通信エラー発生 (ステータス: ${error.statusCode}): ${error.message}`);
// NetworkError 独自のメソッドも型エラーなく呼び出せる!
if (error.isRetryable()) {
console.log(“リトライを実行します…”);
}
return;
}
if (error instanceof ValidationError) {
// ✅ このブロックの中では、error は ValidationError 型として扱われる
console.warn(“入力バリデーションエラー:”, error.fields);
// 例: フォームの該当フィールドにエラーメッセージを表示する処理など
return;
}
// 万が一、想定外のエラー(TypeErrorや普通のErrorなど)が飛んできた場合
if (error instanceof Error) {
console.error(“予期せぬエラー:”, error.message);
return;
}
// プリミティブ値(文字列など)が throw された場合のフォールバック
console.error(“未知のエラー:”, error);
}
}
このコードの美しいところは、`catch (error: unknown)` という「正体不明のブラックボックス」から、`instanceof` を通すことで、安全に具象クラスの機能(`statusCode`, `fields`, `isRetryable()` など)を安全に取り出せている点だ。
—
⚠️ 忘れてはならないシニアの警告(注意点)
`instanceof` は強力だが、実務で使う際にはいくつか「罠」がある。プロとしてこれらを踏まえておいてほしい。
1. 異なるRealm(iframeやWeb Worker)の壁
もし君のフロントエンドアーキテクチャが、`iframe` を使っていたり、`Web Worker` とメッセージのやり取りを行っている場合、異なるコンテキスト(Realm)間で生成されたオブジェクトは、プロトタイプが別物扱いになる。
つまり、親ウィンドウで作った `class A` と、iframe内で作った `class A` は、見かけが同じでも `instanceof` が `false` を返す。SPAの通常開発ではあまり踏まない地雷だが、ブラウザ拡張機能や複雑なウィジェットを開発する際には頭の片隅に入れておこう。
2. プレーンなオブジェクト(DTO)には無力
サーバーから返ってくるJSONを、単なるインターフェース(`interface User`)や型エイリアス(`type User`)として定義している場合、それは単なるJavaScriptのプレーンなオブジェクト(`{ id: 1, name: “Taro” }`)であり、クラスのインスタンスではない。
したがって、`response instanceof User` と書いても、TypeScriptはコンパイルエラーにするか、実行時に関数ではなく型名を参照しようとして怒られる。プレーンなJSONオブジェクトを検証したい場合は、`instanceof` ではなく、ユーザー定義の型ガード関数(User-Defined Type Guards: `value is User`) や、Zodなどのバリデーションライブラリを組み合わせるのが正解だ。
—
まとめ
型ガードとしての `instanceof` は、単なる「条件分岐のテクニック」ではない。TypeScriptの静的型システムと、JavaScriptのプロトタイプベースの動的ランタイムを優雅に橋渡しする、極めてロバストな手法だ。
「とりあえず `as` でキャストする」という悪癖を今日で卒業し、`instanceof` を使ってランタイムの安全性まで担保された美しいコードを書こう。チームのコードレビューで、君が書いたスマートな型ガードのロジックを見るのが今から楽しみだよ。
さて、コーヒーブレイクにでもしたら、次のタスクに取りかかろうか。何か質問があればいつでも声をかけてくれ。

コメント