Prompt

Tsにおけるis, as, typeof, instanceof, inあたりの方に関する構文の話…

TypeScriptを使いこなす上で、これら 「型を守り、型を導く」 構文たちは、ランタイムの挙動(JavaScript)とコンパイル時の型(TypeScript)の橋渡しをする超重要ユニットだね!

情報工学を学ぶ君なら、これらを 「型ガード(Type Guards)」 という大きな戦略の中に位置づけて、「実行時の真偽値が、いかにして型システムの絞り込み(Narrowing)に変換されるか」 という視点で解剖すると、その美しさがより際立つはずだよ。

還元主義的に、それぞれの役割と使い分けを整理してみよう!


1. プリミティブを守る typeof

JavaScript標準の typeof 演算子を if 文で使うと、TypeScriptはそのブロック内での型を自動的に絞り込んでくれる。

  • 対象: string, number, boolean, symbol, undefined, object, function などの基本型。

  • 特徴: 最も手軽。

TypeScript

function printId(id: string | number) {
  if (typeof id === "string") {
    // ここでは id は string 型として扱える!
    console.log(id.toUpperCase());
  } else {
    // 消去法で id は number 型になる
    console.log(id.toFixed());
  }
}

2. インスタンスを判別する instanceof

クラスから生成されたオブジェクトが、特定のクラスのインスタンスかどうかを判定する。

  • 対象: new で生成されたオブジェクト(Date, Error, 自作クラスなど)。

  • 強み: プロトタイプチェーンを遡ってチェックしてくれる。

TypeScript

function formatValue(value: Date | string) {
  if (value instanceof Date) {
    return value.toUTCString(); // value は確実に Date
  }
  return value.trim(); // value は string
}

3. プロパティの存在を確認する in

オブジェクトが特定のプロパティを持っているかどうかで型を絞り込む。

  • 対象: interfacetype で定義されたオブジェクトの構造。

  • 強み: クラス化されていない、ただのオブジェクト(リテラル)の判別に非常に強力。

TypeScript

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    return animal.swim(); // Fish型に絞り込まれる
  }
  return animal.fly(); // Bird型に絞り込まれる
}

4. 独自の型ガードを作る is (User-Defined Type Guards)

「特定の関数が true を返したら、その引数はこの型である」とコンパイラに 断言 するための構文だよ。

  • 構文: 引数名 is 型

  • 使い所: typeofin だけでは判別できない複雑なロジックを関数に切り出したい時。

TypeScript

// 戻り値の型が boolean ではなく "arg is Fish" なのがポイント
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

if (isFish(pet)) {
  pet.swim(); // 安全に swim が呼べる!
}

5. 型の強制と回避:as

これは型ガード(絞り込み)ではなく、「型アサーション(断言)」 だね。

  • 役割: 「君(コンパイラ)はこう言ってるけど、僕(人間)はこの型だと知ってるから、黙ってて!」という命令。

  • リスク: 実行時のチェックは一切行われない。間違った as はランタイムエラーの温床になる。

  • 使い所: DOM 操作など、TypeScript側で具体性が欠けている情報を補完する時。


豆知識:タグ付きユニオン(Discriminated Unions)

実は、君の SNS プロジェクトなどで最も多用することになるのは、これらと 「共通のプロパティ(タグ)」 を組み合わせた絞り込みだよ。

TypeScript

type Success = { status: "success"; data: string };
type Failure = { status: "error"; message: string };

function handleResponse(res: Success | Failure) {
  if (res.status === "success") {
    // status というリテラル型のおかげで、res は Success に確定する
    console.log(res.data);
  }
}

落とし穴:typeof null の罠

情報工学の歴史的なバグとして有名だけど、JavaScript では typeof null"object" を返す。

だから if (typeof val === "object") だけで絞り込もうとすると、null が混入して val.someProp で爆死する可能性がある。

「先に if (val !== null) を書く」 というのは、TypeScript 以前の JavaScript からの鉄則だね。


結論:使い分けの指針

  1. 基本的な型(文字列、数値)は typeof

  2. クラスや組み込みオブジェクトは instanceof

  3. オブジェクトのプロパティ有無は in

  4. 複雑な条件で関数化したいなら is

  5. どうしようもない時、あるいは型を上書きしたい時だけ as

君のデジタルガーデン(garden.asappy.xyz)で、例えば「マークダウンのパース結果」を処理する時なんかには、この inis が大活躍するはずだよ。

次は、これらを組み合わせて 「絶対にランタイムエラーを起こさない最強のバリデーション関数」 を作ってみる?それとも、「Rust のパターンマッチング(match)と TS の絞り込みの決定的な違い」 について語り合う?

Would you like me to provide a complex TypeScript example that combines these type guards with generics?