こんにちは。フロントエンドチームのシニアアーキテクトだ。
日々、君たちが書いているPull Requestを見ていると、TypeScriptを「ただのエラー避けの壁」として使っているのか、それとも「信頼性の高いシステムを構築するための武器」として使いこなしているのかが手に取るように分かる。
特に、APIレスポンスやユーザー入力を扱うとき、`any` や `as`(型アサーション)で強引に型をねじ伏せているコードを見かけると、夜も眠れなくなる(実際はぐっすり寝てるけどね)。
さて、今回は中級から一歩抜け出すための登竜門、`typeof` による型ガード(Type Guard) について徹底的に解説しよう。
公式ドキュメントをただなぞるような退屈な話はしない。JavaScriptのランタイムの動きと、TypeScriptのコンパイラがどう連携しているのか、その「裏側のメカニズム」から実務で即座に使えるテクニックまで、骨太に伝授しよう。
—
なぜ `typeof` 型ガードが必要なのか?(ランタイムと静的型のギャップ)
TypeScriptを書く上で、絶対に忘れてはならない大前提がある。
それは、「TypeScriptの型情報は、コンパイル(トランスパイル)された後にはすべて消え去る」 ということだ。ブラウザが実行しているのは、ただのピュアなJavaScriptだ。
例えば、サーバーから返ってきたデータが `string` なのか `number` なのか、TypeScriptのコンパイル時には分かっていても、実際にブラウザがそのコードを実行する「実行時(ランタイム)」まで確定しないケースは山ほどある。
ここで、典型的なダメなコードを見てみよう。
// ❌ やりがちなダメな例:型アサーション(as)でコンパイラを言いくるめる
function processValue(value: unknown) {
// 「俺を信じろ!これはstringだ!」とコンパイラに嘘をつく
const upper = (value as string).toUpperCase();
console.log(upper);
}
// 実行時に number が渡ってきたらどうなるか?
processValue(100); // 💥 轟沈:Uncaught TypeError: value.toUpperCase is not a function
TypeScriptは静的型付け言語だが、JavaScriptの動的な側面(特に外部からの入力)を完全に防ぐことはできない。だからこそ、「実行時の安全なチェック」と「TypeScriptの型推論」を同期させる仕組みが必要になる。それが `typeof` 型ガードだ。
—
`typeof` 演算子:JavaScriptの機能からTypeScriptの型ガードへ
JavaScriptの `typeof` は、変数や式のデータ型を文字列で返す古くからの演算子だ。
おさらいだが、JSの `typeof` が返す文字列は以下の7つしかない:
- `”string”`
- `”number”`
- `”bigint”`
- `”boolean”`
- `”symbol”`
- `”undefined”`
- `”object”` (※ `null` が `”object”` になるという有名な歴史的バグがある)
- `”function”`
TypeScriptのすごいところは、このJavaScriptの標準的な `typeof` による条件分岐を、コンパイラが「型を絞り込む(Narrowing)ためのシグナル」として解釈する点にある。
基本的な使い方
百聞は一見にしかず。実務でそのまま使える、プリミティブ型を安全にハンドリングするユーティリティ関数のコードを見てほしい。
/
- ユーザーからの入力やAPIのレスポンスなど、
- 何が来るか分からない(unknown)値を受け取り、安全に処理する関数
/
function formatInput(input: unknown): string {
// 1. string型に絞り込む(typeof ガード)
if (typeof input === “string”) {
// このブロック内では、コンパイラは input が string であると確信している!
// だから .trim() が補完され、安全に呼び出せる。
return input.trim().toUpperCase();
}
// 2. number型に絞り込む
if (typeof input === “number”) {
// このブロック内では input は number として扱われる
return input.toFixed(2);
}
// 3. boolean型に絞り込む
if (typeof input === “boolean”) {
// このブロック内では input は boolean として扱われる
return input ? “Yes” : “No”;
}
// ここに到達した時点で、input は string, number, boolean 以外であることが確定する
return “Unknown Type”;
}
// 動作確認
console.log(formatInput(” hello world “)); // “HELLO WORLD”
console.log(formatInput(42.195)); // “42.20”
console.log(formatInput(true)); // “Yes”
console.log(formatInput({ id: 1 })); // “Unknown Type”(安全に弾かれる)
このコードの何が美しいか?
`as` を使ってコンパイラに嘘をつく必要が一切ない。ランタイムで安全性を担保した上で、TypeScriptのコンパイラが自律的に型を確定(Narrowing)させてくれているのだ。
—
現場で役立つ実践テクニック:配列と `typeof` の罠
さて、ここからがシニアの腕の見せ所だ。
基本のプリミティブ型が分かったところで、現場でよくハマる「配列(Array)」の判定について話そう。
「よし、配列かどうかを判定するぞ! `typeof` を使おう!」と思ってこう書くエンジニアがいる。
// ❌ 惜しい、というか完全に間違いの例
function processList(items: unknown) {
if (typeof items === “object”) {
// ここで items はオブジェクトだと判定されるが…
// 実は配列(Array)も、JavaScriptでは typeof で “object” になってしまう!
// さらに、null も typeof で “object” になる!
}
}
そう、JavaScriptの仕様上、`typeof null` は `”object”` であり、`typeof []` も `”object”` だ。
配列を安全に判定したい場合は、`typeof` ではなく、標準の `Array.isArray()` を使うのが正解だ。TypeScriptは `Array.isArray()` も型ガードとして認識してくれる。
では、「数値の配列(`number[]`)」のなかにあるプリミティブ値を安全に取り出す処理を例に、実用的なコードを見てみよう。
/
- 配列、または単一のプリミティブ値を受け取り、
- それぞれの型に応じた処理を行う堅牢な関数
/
function processFlexibleData(data: unknown): void {
// パターンA: 配列の判定には Array.isArray を使う
if (Array.isArray(data)) {
// この中では data は `unknown[]` として推論される
data.forEach((item) => {
// さらに要素の型を typeof でガードする
if (typeof item === “number”) {
console.log(`数値アイテム: ${item 2}`);
} else if (typeof item === “string”) {
console.log(`文字列アイテム: ${item.length}文字`);
}
});
return;
}
// パターンB: 単一のプリミティブ値の判定に typeof を使う
if (typeof data === “string”) {
console.log(`単一の文字列: ${data}`);
} else if (typeof data === “number”) {
console.log(`単一の数値: ${data}`);
} else {
console.warn(“サポートされていないデータ型です”);
}
}
// 実行テスト
processFlexibleData([10, “apple”, 30]);
// 出力:
// 数値アイテム: 20
// 文字列アイテム: 5文字
// 数値アイテム: 60
processFlexibleData(“just a string”);
// 出力: 単一の文字列: just a string
このように、`Array.isArray()` と `typeof` を組み合わせることで、外部から飛んできた構造の読めないデータ(`unknown`)を、上流から下流まで完全にコントロール下におけるようになる。
—
チーフアーキテクトからのまとめとベストプラクティス
1. `any` や `as` への安易な逃げを禁止する
外部APIやフォーム入力を受け取る境界線(Boundary)では、必ず `unknown` で受け取り、`typeof` や `Array.isArray` を使った型ガードで安全に型を絞り込むこと。
2. JavaScriptのランタイム特性を理解する
`typeof null === “object”` や `typeof [] === “object”` といったJavaScriptの歴史的仕様を頭に入れ、プリミティブ型以外を判定する際は専用のガード(`Array.isArray` など)を使い分けること。
3. 網羅性チェック(Exhaustiveness Checking)を意識する
`never` 型と組み合わせることで、将来的に新しい型が追加された際にコンパイルエラーで気付けるような堅牢なアーキテクチャを目指そう。
型ガードを制する者は、TypeScriptを制する。
明日からのコードレビューでは、無駄な `as string` があったら容赦なく突っ込んでやってくれ。君たちのコードがより洗練されることを期待しているよ!

コメント