TypeScript | 基礎文法:Union・基本型操作 – union型とif分岐

TypeScript
スポンサーリンク

union型とif分岐の関係をまずイメージでつかむ

union型は「A か B かどちらか」という“選択肢のある型”でした。
そして if 分岐は、「今この瞬間は A なのか B なのかを確かめるための問い」です。

TypeScript は、if の条件を読んでこう考えます。

「この条件が true なら、この変数はこの型のはず」
「false なら、残りの型のどれかのはず」

つまり、union型と if はセットで使うことで、「型を絞り込む」ための強力な道具になります。
ここを具体例で丁寧に見ていきます。


基本:string | number を typeof で分ける

union型のままだと「できること」が制限される

まず、典型的な string | number の union 型を考えます。

function printFormatted(id: string | number) {
  // console.log(id.toUpperCase()); // エラー
}
TypeScript

このとき TypeScript は、

「id は string かもしれないし、number かもしれない。
string にしかない toUpperCase を、そのまま呼ぶのは危険。」

と判断してエラーを出します。

union 型のままでは、「両方の型に共通するもの」しか直接使えません。
ここで登場するのが if 分岐です。

typeof を使った if で型を絞り込む

function printFormatted(id: string | number) {
  if (typeof id === "string") {
    console.log("文字列ID:", id.toUpperCase());
  } else {
    console.log("数値ID:", id.toFixed(2));
  }
}
TypeScript

このコードで起きていることを、型の目線で分解します。

関数の入り口では、id の型は string | number
if (typeof id === "string") の中に入った瞬間、
TypeScript は「ここでは id は string」とみなします。

だから、id.toUpperCase()id.length も安全に呼べるようになります。

一方、else の中では「string ではない」ということが分かっているので、
残りの number に絞り込まれ、id.toFixed(2) が許されます。

つまり、if 分岐によって「string の世界」と「number の世界」をきれいに分け、
それぞれの世界に合ったメソッドだけを安全に使っている
わけです。


null を含む union型と if 分岐(nullチェック)

string | null を if で絞り込む

よく出てくるのが、string | null です。

function greet(name: string | null) {
  // console.log(name.toUpperCase()); // エラー
}
TypeScript

name が null かもしれないので、このままでは toUpperCase を呼べません。
ここでも if が効いてきます。

function greet(name: string | null) {
  if (name === null) {
    console.log("名前がありません");
    return;
  }

  console.log("こんにちは、" + name.toUpperCase());
}
TypeScript

最初 namestring | null
if (name === null) の中では name は null。
return で null のパターンを処理してしまうと、その下の行では

「ここまで来ているということは、もう null ではない」
→ 「つまり、ここでは name は string だ」

と TypeScript が理解します。

この「先に null を弾いて正常系だけの世界にする」書き方は、実務でもものすごく使われます。
if 分岐は、「異常なパターンを union 型から取り除き、残りを安全な型だけにする」ためのフィルターだと捉えてください。


オブジェクトの union型と if 分岐(in 演算子)

「このプロパティを持っているか」で分ける

次はオブジェクトの union 型です。

type User = {
  name: string;
};

type Admin = {
  name: string;
  permissions: string[];
};

type Person = User | Admin;
TypeScript

PersonUserAdmin のどちらかです。
Admin だけが permissions プロパティを持っています。

では、「管理者だけ別の処理をしたい」という関数を考えます。

function printPerson(person: Person) {
  if ("permissions" in person) {
    console.log("管理者:", person.name, person.permissions.join(", "));
  } else {
    console.log("一般ユーザー:", person.name);
  }
}
TypeScript

ここでの if 分岐は、
permissions というプロパティを持っているか?」を聞いています。

"permissions" in person が true の世界
permissions を持つ型(Admin)に絞り込まれる

false の世界
→ 残りの型(User)に絞り込まれる

というイメージです。

これも、if 分岐によって「Admin の世界」と「User の世界」を分けていると捉えられます。


リテラルunion型と if / switch 分岐

状態を “idle” | “loading” | “success” | “error” などで表す

もう少し実務寄りの例を出します。

type LoadingState = 
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; error: string };
TypeScript

この LoadingState は、「非同期処理の状態」を4パターンの union 型で表現しています。

if 分岐でこれを使うと、こうなります。

function render(state: LoadingState) {
  if (state.status === "idle") {
    console.log("待機中");
  } else if (state.status === "loading") {
    console.log("読み込み中…");
  } else if (state.status === "success") {
    console.log("成功:", state.data);
  } else {
    console.log("失敗:", state.error);
  }
}
TypeScript

ここで起きている型の絞り込みはこうです。

state.status === "idle" の中
→ state は { status: "idle" }

state.status === "loading" の中
→ state は { status: "loading" }

state.status === "success" の中
→ state は { status: "success"; data: string }(だから state.data が使える)

最後の else
→ 残りは { status: "error"; error: string }(だから state.error が使える)

1つの union 型を、if の連続で「状態ごとの世界」に分けているわけです。
if の条件と型の関係がきれいに揃っていると、TypeScript はそこに乗っかってちゃんと型を絞ってくれます。


union型とif分岐を設計として使う感覚

常に「今このブロックで、この変数は何型の“つもり”で書いているか」を意識する

union 型と if を使いこなす鍵は、
if 文を書くたびに自分にこう問いかけることです。

この条件が true のとき、この変数は本当は何型の“つもり”で扱いたい?
false のときは、何型の“つもり”?

たとえば、次のようなコードを書くとき。

function handle(value: string | number | null) {
  if (value === null) {
    // ここでは value を「値なし」として扱う
  } else if (typeof value === "string") {
    // ここでは value を string として扱う
  } else {
    // 残りは number
  }
}
TypeScript

これは、

「null の世界」
「string の世界」
「number の世界」

という3つの世界に union 型を分解している、とも言えます。

if を書きながら、
「このブロックの中では、この変数はこの型だと信じて書いている」
という自分の感覚と、TypeScript の型推論が一致しているかを見る。

一致していなければエラーになります。
そのエラーは、あなたの「思い込み」と「実際の型」がズレていることを教えてくれます。

union型+if は「曖昧なものを、はっきりした複数の世界に割る」ための道具

union 型は「あれかこれか、どれか」です。
if 分岐は、「今はどれなの?」を確かめるための質問です。

TypeScript は、その質問の仕方(typeof, ===, "prop" in obj など)を見て、
「じゃあこの中ではこの型、この中ではこの型」と世界を分けてくれます。

コードを書いていて、

この値は複数パターンありうるな(union型)
パターンごとにやりたいことが違うな(if 分岐)

と思ったら、そこが “union 型+if 分岐で型を絞るポイント” です。

その分岐をサボらず、
「このパターンのときはこうする」「このパターンのときはこうする」と丁寧に書いた分だけ、
TypeScript はあなたの意図を正確に理解してくれます。

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