TypeScript | 基礎文法:関数の基礎 – 条件分岐と型推論

TypeScript
スポンサーリンク

「条件分岐すると型も変わる」という感覚から始める

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

最初 namestring | 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 の中では personAdmin に、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;
  }
}
TypeScript

type プロパティの値が union されていて、
それと同じ名前のプロパティで switch しているので、TypeScript は

  • case "user"User
  • case "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);
  }
}
TypeScript

isAdmin(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 の推論結果を揃えていく作業こそが、
条件分岐と型推論を“設計レベルで味方につける”ということです。

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