【実務・中級編】 typeof演算子による型ガード – TypeScript実践ガイド

こんにちは。君、最近のコードベースを見ていて「また `any` が生えているぞ」なんて頭を抱えていないかい?

中級の壁を突破してシニアの領域に踏み込もうとしている君なら、APIから返ってくるデータや、外部ライブラリの気まぐれな入力値に振り回される恐怖が痛いほどわかるはずだ。「これは本当に string なのか? それとも undefined なのか?」ってね。

今回は、そんなフロントエンドの現場で毎日のように直面するカオスを、TypeScriptの `typeof` 演算子による型ガードで鮮やかに切り伏せる方法を伝授しよう。公式ドキュメントをただなぞるような退屈な話はしない。ブラウザの裏側の挙動や、現場で本当に使える実践的なテクニックまで深く掘り下げていくから、心してついてきてほしい。

—

1. なぜ `typeof` 型ガードが必要なのか?(現場のリアルな課題)

フロントエンド開発において、最もバグの温床になりやすい瞬間はどこか? そう、「外部との境界線」だ。バックエンドから受け取るJSON、`localStorage` から取り出すデータ、あるいはユーザからのDOM入力。これらはすべて、TypeScriptの静的な世界から見れば「信頼できない野良データ」だ。

例えば、こんなコードを書いていないかい?

// ❌ 現場でやりがちなダメな例
function formatInput(input: string | number) {
// 動くには動くが、toUpperCase() が number に存在しないため
// コンパイルエラーになるか、無理やり as string で型アサーションしてバグの種をまく
return input.toUpperCase();
}

ユニオン型(`string | number` のように「あれかこれか」の型)として受け取った値に対して、そのままメソッドを生やすことはできない。TypeScriptのコンパイラは、「今、この変数が何者であるか」を文脈から読み取る必要がある。

ここで登場するのが `typeof` 演算子だ。これを使うことで、「コンパイル時の型の世界」と「実行時のJavaScriptの世界」を美しく同期させることができる。

—

2. TypeScriptの `typeof` と JavaScriptの `typeof` の二面性

ここで少し、裏側の仕組みの話をしよう。TypeScriptの `typeof` は、実は2つの顔を持っている。

1. JavaScriptの実行時演算子としての `typeof`
ブラウザ(V8などのJSエンジン)が実行時に値のデータ型を文字列で返す仕組み(`”string”`, `”number”`, `”boolean”`, `”object”` など)。
2. TypeScriptの型クエリとしての `typeof`
変数の定義から型を抽出する機能(例: `const user = { name: “Taro” }; type User = typeof user;`)

今回焦点を当てるのは、1つ目の「実行時演算子としての `typeof`」だ。
TypeScriptのコンパイラは非常に賢い。私たちがランタイムで `typeof x === “string”` という条件分岐(Control Flow Analysis / 制御フロー分析)を書くと、コンパイラはそれを静的に検知し、「このブロックの中では、`x` の型は `string` に絞り込まれている(Narrowing)」と自動で解釈してくれるんだ。

—

3. 実務で即コピペできる! `typeof` 型ガードの実践パターン

百聞は一見にしかずだ。実際のプロジェクトでそのまま使える、堅牢なユーティリティ関数のコードを見ていこう。

/

  • ユーザーからの入力値(string | number | boolean)を受け取り、
  • それぞれに応じた安全なフォーマット処理を行う関数

/
function smartFormatter(value: string | number | boolean): string {
// 1. string の絞り込み
if (typeof value === “string”) {
// このブロック内では、TypeScriptは value を string型 として扱う
// よって、string専用のメソッドが補完され、安全に呼び出せる
return value.trim().toUpperCase();
}

// 2. number の絞り込み
if (typeof value === “number”) {
// このブロック内では number型
// NaN や負数のチェックもここで行える
if (Number.isNaN(value)) {
return “Invalid Number”;
}
return value.toFixed(2);
}

// 3. boolean の絞り込み
if (typeof value === “boolean”) {
// このブロック内では boolean型
return value ? “Yes” : “No”;
}

// 万が一、将来的に型が追加された場合の網羅性チェック(Exhaustiveness check)
// Never型を利用することで、コンパイル時に未処理の分岐がないかを担保できる
const exhaustiveCheck: never = value;
return exhaustiveCheck;
}

このコードの美しいポイント

  • 型アサーション(`as string` など)を一切使っていない。型アサーションは「俺を信じてくれ!」というコンパイラへの嘘になりがちだが、`typeof` 型ガードは事実に基づいた安全な絞り込みだ。
  • 最後の `never` による網羅性チェック。もし将来、型に `null` や `object` が追加されたとき、このコードはコンパイルエラーを起こして「おい、新しい型の処理が抜けているぞ」と教えてくれる。これがシニアの組む堅牢なアーキテクチャだ。

—

4. 知っておくべき `typeof` の「闇」と落とし穴

さて、ここまで持ち上げておいて何だが、JavaScriptの歴史的経緯が生んだ `typeof` の最大のトラップについても触れておかなきゃならない。実務で必ずハマるポイントだ。

それは、`typeof null === “object”` という仕様のバグ(JavaScript黎明期の遺産)だ。

const data: string | null = null;

if (typeof data === “object”) {
// ここに入る!しかも data は null として扱われるため、
// うっかり data.property などにアクセスすると “Cannot read properties of null” で爆死する。
}

配列(Array)も同様に `typeof` では `”object”` と判定されてしまう。そのため、`typeof` が効果を発揮するのは、あくまで記事のテーマであるプリミティブ型(`string`, `number`, `boolean`, `bigint`, `symbol`, `undefined`)の絞り込みにおいてだ。

オブジェクトや配列、あるいは `null` を安全に絞り込みたい場合は、`Array.isArray()` や、カスタム型ガード関数(User-Defined Type Guards)、あるいは `instanceof` を組み合わせる必要がある。ここを混同すると、痛い目を見るので覚えておいてほしい。

—

5. まとめ

`typeof` 演算子による型ガードは、TypeScriptを書く上で最も基本的でありながら、コードの安全性を底上げする最強の武器の一つだ。

  • 外部から来る不確実なデータは、まずプリミティブ型のユニオンとして受け取る。
  • `typeof` による条件分岐で、TypeScriptの制御フロー分析に型を絞り込ませる。
  • 型アサーション(`as`)逃げをせず、コンパイラの検査能力を最大限に活かす。

この基本原則をチームに浸透させるだけで、プロダクトのランタイムエラーは劇的に減る。明日からのコードレビューで、誰かが `any` や不要な `as` を書いていたら、ぜひこの知見をシェアしてあげてくれ。

それじゃあ、今日もイカしたコードを書こうぜ!

コメント

タイトルとURLをコピーしました