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()); // エラー
}
TypeScriptname が null かもしれないので、このままでは toUpperCase を呼べません。
ここでも if が効いてきます。
function greet(name: string | null) {
if (name === null) {
console.log("名前がありません");
return;
}
console.log("こんにちは、" + name.toUpperCase());
}
TypeScript最初 name は string | 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;
TypeScriptPerson は User か Admin のどちらかです。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 はあなたの意図を正確に理解してくれます。
