typeofによる型ガードとは何か
typeof による型ガードは、「値の実行時の型をチェックして、その結果をもとに TypeScript が変数の型を“狭くする”仕組みです。
人間の感覚としてはこうです。
「typeof value === "string" ってチェックしたんだから、この中では value は string のはずだよね?」
TypeScript もそれを理解してくれます。
そのおかげで、string にしかないメソッド(toUpperCase など)を、安全に呼べるようになります。
ここから、実際のコードを通してこの感覚をはっきりさせていきます。
基本:union型をtypeofで安全に分岐する
string | number を typeof で分ける
まず、いちばん典型的な例から。
function printId(id: string | number) {
if (typeof id === "string") {
// ここでは id は string 型に絞り込まれている
console.log("文字列ID:", id.toUpperCase());
} else {
// ここでは id は number 型に絞り込まれている
console.log("数値ID:", id.toFixed(2));
}
}
TypeScriptポイントを整理します。
関数の引数 id の型は最初 string | number。if (typeof id === "string") と書いたブロックの中では、
TypeScript は「ここでは id は string 型」と判断します。
だから id.toUpperCase() や id.length がエラーなく使えます。
逆に else の中では「string じゃないんだから number だろう」と判断されます。
typeof による型ガードは、
「union 型のうち、ある一部だけを取り出す“フィルター”のようなもの」 だと捉えてください。
unknown からの「安全な取り出し」にも使える
unknown は「よく分からない何か」を表す型です。
function printLength(value: unknown) {
if (typeof value === "string") {
// ここでは value は string
console.log(value.length);
} else {
console.log("文字列ではありません");
}
}
TypeScriptvalue: unknown のままだと value.length はもちろんエラーですが、typeof value === "string" の中だけは safe な string として扱えます。
「unknown をガチャっと開ける鍵」のひとつが typeof です。
typeofで判別できる型と、できない型
typeof が返す文字列
JavaScript(TypeScript)で typeof を使うと、だいたい次のような文字列が返ってきます。
typeof "foo" // "string"
typeof 123 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 1n // "bigint"
typeof function() {} // "function"
typeof {} // "object"
typeof null // "object" (歴史的な仕様…)
TypeScriptTypeScript の型ガードとして特に使うのは、"string", "number", "boolean", "bigint", "symbol", "undefined", "function", "object" です。
typeof では見分けられないもの
typeof では、
配列かどうか
特定のクラスかどうか(Date かどうか など)
オブジェクトの詳細な構造
は判別できません。
例えば配列はこうなります。
typeof [1, 2, 3]; // "object"
TypeScript配列だけを見分けたい場合は、Array.isArray(value) を使います。
クラスインスタンスを見分けたい場合は、value instanceof Foo を使います。
「typeof は“ざっくりした種類”を見るもの」
と覚えておいてください。
typeofによる型ガードで「正しく絞り込む」例と「絞り込み不足」の例
良い例:string と number をきっちり分ける
function formatValue(value: string | number) {
if (typeof value === "string") {
return value.trim();
} else {
return value.toFixed(2);
}
}
TypeScriptここでは、
if ブロック内:value は string
else ブロック内:value は number
と完全に絞り込めています。
string 専用メソッド trim とnumber 専用メソッド toFixed を、安全に呼べています。
絞り込みが足りない例
function printValue(value: string | number | boolean) {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else {
// ここでは value は number | boolean
// 型がまだ union のまま
// console.log(value.toFixed(2)); // エラー: boolean に toFixed はない
}
}
TypeScriptstring 以外を一緒くたに else で扱っているので、value の型は still number | boolean のままです。
ここで toFixed を呼ぶと、「boolean かもしれないよ」と怒られます。
もし number だけを扱いたいなら、さらに typeof で絞り込む必要があります。
if (typeof value === "string") {
// string
} else if (typeof value === "number") {
// number
} else {
// boolean
}
TypeScript「typeof でチェックしたぶんだけ型が狭くなる。残りは union のまま」
というイメージを持ってください。
typeofによる型ガードと関数の設計
関数の中で「型の世界を2つに分割する」イメージ
もう少し設計寄りの話をします。
関数の中で typeof を使うとき、実はこんなことをやっています。
この関数は、「string として扱える場合」と「それ以外」の2つの世界に分かれる
→ string の世界では string 前提の処理を書き
→ それ以外の世界では、その型に合った処理を書く
function handleInput(input: string | number | null) {
if (typeof input === "string") {
// string の世界
console.log("文字列:", input.toUpperCase());
return;
}
if (typeof input === "number") {
// number の世界
console.log("数値:", input.toFixed(2));
return;
}
// 残りは null の世界
console.log("値なし");
}
TypeScriptここでは、
1つ目の if: string を取り除く
2つ目の if: number を取り除く
最後: 残り(null)のケースだけが残る
という形で、型を順々に削っていっています。
「typeof を使うと、関数内で“型ごとの世界”をきれいに分割できる」
という感覚が持てると、型ガードを設計の武器として使えるようになります。
typeofを使うときに意識してほしい「設計の視点」
どの型のときに、どんな振る舞いをさせたいか
typeof による型ガードを書く前に、こう自分に問いかけてみてください。
この値が string のとき、何をしたい?
この値が number のとき、何をしたい?
それ以外のときは、どう扱うべき?
その答えを、if + typeof の形に落とし込んでいきます。
例えばログ出力を考えたとき、
function log(value: string | number | boolean) {
if (typeof value === "string") {
// 文字列はそのまま
console.log(value);
} else if (typeof value === "number") {
// 数値はフォーマットを統一
console.log(value.toFixed(2));
} else {
// boolean は true/false を強調
console.log(value ? "TRUE" : "FALSE");
}
}
TypeScript「string の世界」「number の世界」「boolean の世界」
それぞれでやりたいことが違うから、型ガードで世界を分けているわけです。
型ガードは「自分の直感とコンパイラの理解を一致させる」ためのもの
typeof で if を書きながら、常にこう感じてみてください。
ここを通ったなら、もうこの変数は string だと“思って”書いているよな
ここを通ったなら、もう number に決まっているよな
その「思い」を、TypeScript にもわかってもらうのが typeof 型ガードです。
もし TypeScript がエラーを出してきたら、それはたいてい、
「その“思い”は本当に正しい? この場合はこういう型もありえるよ?」
と指摘してくれているサインです。
typeof による型ガードは、
ただのテクニックではなく、あなたの頭の中の「型のイメージ」と、コンパイラの「型の理解」を揃えるための会話の道具です。
その感覚を持ちながら、
string / number / boolean / undefined / function / object
あたりの世界を、少しずつ安全に分ける練習をしてみてください。
それが、型ガードを「文法」から「設計の武器」に変える一歩になります。
