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

こんにちは。フロントエンドチームのシニアアーキテクトだ。
日々、君たちが書いている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` があったら容赦なく突っ込んでやってくれ。君たちのコードがより洗練されることを期待しているよ!

コメント

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