「条件分岐すると型も変わる」という感覚から始める
TypeScript のおいしいところは、
if や switch で条件分岐すると、「そのブロックの中だけ型が狭くなる」 ことです。
「条件でチェックした内容を、TypeScript がちゃんと理解してくれる」
これを 型推論(type narrowing / 型の絞り込み) と言います。
最初は難しそうに見えるかもしれませんが、やっていることはシンプルです。
「この if を通ったなら、この変数はもうこれこれの型のはずだよね?」
という、人間の直感を TypeScript が一緒に追いかけてくれているだけです。
ここからは、その直感をコードと型で一緒に確認していきましょう。
まずは unknown / union 型を if で絞り込む
typeof での基本的な型の絞り込み
function printLength(value: unknown) {
if (typeof value === "string") {
// ここに入った時点で、value は string 型に絞られる
console.log(value.length);
} else {
// ここでは、value は "string 以外" に絞られている
console.log("文字列ではありません");
}
}
TypeScriptポイントは2つあります。
1つ目は、引数 value の型が最初は unknown だということ。
「何かは来るけど、まだ型はわからない」という状態です。
2つ目は、if (typeof value === "string") の中に入ってきた時点で、
TypeScript は
「typeof で string だと確認した」
→ 「じゃあここでは value を string として扱っていい」
と判断すること。
だから value.length にエラーが出ません。
「型チェックと if の条件がリンクしている」 というのが大事な感覚です。
union 型を if で分ける
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」のどちらか、という union 型です。
typeof id === "string" の中では、
「union のうち string だけ」に絞られます。
逆に else の中では、
「string ではない方」、つまり number に絞られます。
「union 型が、条件分岐によって一つずつ剥がれていく」 というイメージを持ってください。
null / undefined を条件で弾くと、型が「非 null」になる
null チェックで「値あり」として扱えるようになる
function printName(name: string | null) {
if (name === null) {
console.log("名前は未設定です");
return;
}
// ここに来た時点で name は string に絞られている
console.log("こんにちは、" + name.toUpperCase());
}
TypeScript最初 name は string | null です。
if (name === null) で null のケースを先に処理して return したあと、
その下の行に来た時点では、TypeScript はこう理解しています。
「null のときはさっき return したから、ここまで来るのは null じゃないときだけ」
→ 「つまりここでは name は string だけ」
だから name.toUpperCase() が OK になります。
これはかなり頻出パターンで、
- エラーチェックや early return で「異常系」を先に分岐させる
- それ以降の処理では「型が絞り込まれた、正常系だけの世界」が広がる
という構図です。
TypeScript 的にも実務的にも、とても相性のいい書き方です。
in / プロパティチェックで「オブジェクトの型」を絞り込む
オブジェクトの union 型をプロパティの有無で分ける
type User = {
type: "user";
name: string;
};
type Admin = {
type: "admin";
name: string;
permissions: string[];
};
type Person = User | Admin;
TypeScriptこういう union 型があるとします。
function printPerson(person: Person) {
if ("permissions" in person) {
// ここでは person は Admin 型に絞られる
console.log("管理者:", person.name, person.permissions);
} else {
// ここでは person は User 型
console.log("一般ユーザー:", person.name);
}
}
TypeScript"permissions" in person というチェックは、
「このオブジェクトに permissions というプロパティがあるか?」を見ています。
Person の union のうち、そのプロパティを持っているのは Admin だけ。
だから if の中では person が Admin に、else の中では User に絞られます。
「型の違いを“どのプロパティを持っているか”として設計しておくと、if で自然に型が絞り込める」
この感覚は、オブジェクト設計と型推論をつなげるうえでとても大事です。
判別可能な union と switch での型推論
kind / type プロパティで「バリエーション」を見分ける
さっきの Person を、type で分けるパターンに変えてみます。
type User = {
type: "user";
name: string;
};
type Admin = {
type: "admin";
name: string;
permissions: string[];
};
type Person = User | Admin;
TypeScriptこれを switch で分けると、TypeScript がきれいに型を絞り込んでくれます。
function describe(person: Person) {
switch (person.type) {
case "user":
// ここでは person は User 型
console.log("User:", person.name);
break;
case "admin":
// ここでは person は Admin 型
console.log("Admin:", person.name, person.permissions);
break;
}
}
TypeScripttype プロパティの値が union されていて、
それと同じ名前のプロパティで switch しているので、TypeScript は
case "user"→Usercase "admin"→Admin
と対応づけてくれます。
こういう「特定の文字列リテラルを持つプロパティ(ここでは type)」を
判別用プロパティ と呼びます。
このパターンを使えるようになると、
- union 型のどのパターンも漏れなく処理できているか
- 特定のパターンでだけ存在するプロパティに安全にアクセスできているか
を、TypeScript が一緒に確認してくれるようになります。
型ガード関数(value is 型)と条件分岐
自作の「型を絞り込む関数」を if で使う
もう一歩踏み込むと、
「条件分岐で使う関数そのものが“型を絞り込む”役割を持つ」 こともできます。
function isAdmin(person: Person): person is Admin {
return person.type === "admin";
}
TypeScriptここで普通なら戻り値型は boolean と書きますが、person is Admin という特別な形で書いています。
これを if 文で使うと:
function printPerson(person: Person) {
if (isAdmin(person)) {
// ここでは person は Admin 型
console.log(person.permissions);
} else {
// ここでは person は User 型
console.log(person.name);
}
}
TypeScriptisAdmin(person) が true だったとき、
「戻り値型の宣言どおり、person は Admin のはずだ」と TypeScript が理解し、
型を絞り込んでくれます。
つまり、
- 型ガード関数の中身(条件)は自分が書く
- その関数を if で使ったときの「型の変化」は、戻り値型
person is Adminによって決まる
という構造です。
ここまで来ると、
「条件分岐の書き方(if / switch)だけでなく、“条件の関数の戻り値型”も型推論に効いてくる」
というところまで見えてきます。
条件分岐と型推論を味方につけるための考え方
ここまでの内容を、実際に書くときの「頭の使い方」に落とします。
関数の中で if / switch を書き始めるとき、
まずこう自分に問いかけてみてください。
この条件が true だったら、この変数はどんな型のはず?
逆に false のとき、この変数はどんな型のはず?
TypeScript は、その直感にかなりついてきてくれます。
typeof / === null / "prop" in obj / obj.type === "xxx" などの条件と
その後のブロックでの変数の扱いを見比べて、
「この条件を書いたんだから、ここでこのプロパティに触れるのは安全だよね?」
という感覚で if の中身を書く。
その感覚に慣れてくると、
エラーが「うるさい警告」ではなく、「条件分岐と型の整合性をチェックしてくれる相棒のコメント」 に見え始めます。
条件分岐を書くたびに、
「今、何をチェックしていて、その結果この変数はどんな型だと“思っているのか”」
を意識してみてください。
その「思い」と TypeScript の推論結果を揃えていく作業こそが、
条件分岐と型推論を“設計レベルで味方につける”ということです。
