TypeScript | 基礎文法:関数の基礎 – typeofによる型ガード

TypeScript
スポンサーリンク

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("文字列ではありません");
  }
}
TypeScript

value: 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" (歴史的な仕様…)
TypeScript

TypeScript の型ガードとして特に使うのは、
"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 はない
  }
}
TypeScript

string 以外を一緒くたに 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

あたりの世界を、少しずつ安全に分ける練習をしてみてください。
それが、型ガードを「文法」から「設計の武器」に変える一歩になります。

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