TypeScript | 基礎文法:関数の基礎 – 型ガードの基礎

TypeScript
スポンサーリンク

「型ガード」とは何かを一言でつかむ

まずイメージからいきます。
型ガードは、
「この if を通ったなら、この変数は“この型”だと安全に言えるよ」
ということを、TypeScript にちゃんと分かってもらうための「型の見張り役」です。

人間は自然にこう考えています。

  • typeof value === "string" とチェックした
    → じゃあこの中では value は string だろう

TypeScript にも、この“人間の感覚”を理解させてあげる仕組みが「型ガード」です。
型ガードには大きく2種類あります。

  • 言語組み込みの型ガード(typeof, instanceof, in, リテラルチェック など)
  • 自分で定義する型ガード(ユーザー定義型ガード)

両方とも、「条件分岐で型を安全に絞り込む」という同じ目的を持っています。


組み込みの型ガードで型がどう絞られるか

typeof を使った基本的な型ガード

function printLength(value: string | number) {
  if (typeof value === "string") {
    // ここに入った時点で、value は string 型に絞られる
    console.log(value.toUpperCase());
    console.log(value.length);
  } else {
    // ここでは、value は number 型に絞られる
    console.log(value.toFixed(2));
  }
}
TypeScript

最初 valuestring | number(どっちか)です。
typeof value === "string" と書いた if の中では、

「string であることをチェックした」
→ 「ここでは string として扱っていい」

と TypeScript が理解し、toUpperCaselength が使えるようになります。
逆に else 側では「string ではない」ので number に絞られ、toFixed が使えます。

ここで大事なのは、

「条件として書いたチェック(typeof など)を、TypeScript が“型のヒント”として活用している」
という感覚です。

null / undefined を弾く型ガード

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

  // ここに来た時点で name は string 型に絞られている
  console.log("こんにちは、" + name.toUpperCase());
}
TypeScript

name は最初 string | null です。
if (name === null) で null の場合を先に処理して return すると、
その下では「null じゃない」ことが保証され、string に絞られます。

このパターンは実務で非常によく使います。

「エラー系や“ない”ケースを if で弾く」
→ 「それ以降の処理では、型が安全な世界だけが残る」

この「正常系の世界を型で狭める」のが型ガードの気持ちいいところです。

"プロパティ名" in オブジェクト での型ガード

type User = {
  name: string;
};

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

type Person = User | Admin;

function printPerson(person: Person) {
  if ("permissions" in person) {
    // ここでは person は Admin 型に絞られる
    console.log(person.permissions);
  } else {
    // ここでは person は User 型
    console.log(person.name);
  }
}
TypeScript

"permissions" in person という条件は、
「このオブジェクトが permissions プロパティを持っているか?」を見ています。

Person の union の中で permissions を持っているのは Admin だけなので、
if の中で personAdmin に、else では User に絞られます。

「プロパティの有無を使って型を見分ける」 ― これも自然な型ガードです。


判別可能な union と switch は“型ガードのセット”

type / kind プロパティによる型ガード

こういう union 型をよく定義します。

type Circle = {
  kind: "circle";
  radius: number;
};

type Square = {
  kind: "square";
  size: number;
};

type Shape = Circle | Square;
TypeScript

ここでの kind は「このオブジェクトがどのバリエーションか」を示すラベルです。
これを使って switch すると、TypeScript がきれいに型を絞ってくれます。

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      // ここでは shape は Circle 型
      return Math.PI * shape.radius ** 2;
    case "square":
      // ここでは shape は Square 型
      return shape.size ** 2;
  }
}
TypeScript

shape.kind の値と、union の各型の kind が対応しているので、
case "circle" に入った時点で shapeCircle に、case "square" では Square に絞られます。

「判別用プロパティ(kind / type)+ switch」は、TypeScript がデフォルトで理解している強力な型ガードの組み合わせです。


自分で作る型ガード(ユーザー定義型ガード)の基礎

戻り値を value is 型 と書く特殊な関数

組み込みの型ガードだけでもかなり戦えますが、
TypeScript には 「自分で型ガード関数を定義する」 仕組みもあります。

それが「ユーザー定義型ガード」です。

基本の形はこうです。

function isString(value: unknown): value is string {
  return typeof value === "string";
}
TypeScript

注目してほしいのは戻り値の型です。

(value: unknown): value is string
TypeScript

value is string という形になっています。
これが「この関数は、true を返したとき value が string であることを保証する型ガード関数ですよ」という宣言です。

使うときは if 文などでこうします。

function printValue(value: unknown) {
  if (isString(value)) {
    // ここでは value は string 型に絞られる
    console.log(value.toUpperCase());
  } else {
    // ここでは string 以外(unknown のままなど)
    console.log("string ではありません");
  }
}
TypeScript

isString(value) が true のブロックでは、
TypeScript は「value は string」と扱ってくれるようになります。

ポイントは、「return の値」が true / false かどうかだけでなく、
「戻り値の型」が if の中の「変数の型」を変えていること
です。

union 型を “派生させる” 型ガード

さきほどの Shape を使った型ガードも作れます。

function isCircle(shape: Shape): shape is Circle {
  return shape.kind === "circle";
}
TypeScript

使い方は同じです。

function printShape(shape: Shape) {
  if (isCircle(shape)) {
    // ここでは shape は Circle 型
    console.log("円の半径:", shape.radius);
  } else {
    // ここでは shape は Circle 以外(Square)
    console.log("正方形の一辺:", (shape as Square).size);
  }
}
TypeScript

if の中で shape の型が Circle に絞られるので、radius に安全にアクセスできます。

「型ガード関数 = “この条件を満たしていれば、この型として扱っていい”と教える関数」
と覚えてください。


型ガードをどう設計に使うか

「いつも同じパターンで型チェックしてるな」と思ったら関数化する

コードを書いていて、たとえばこんなチェックを何度も書いているとします。

if (person.type === "admin") {
  // ...
}
TypeScript

あちこちにこれが散らばっているなら、
それを「isAdmin という型ガード関数」にまとめるイメージです。

type User = { type: "user"; name: string };
type Admin = { type: "admin"; name: string; permissions: string[] };
type Person = User | Admin;

function isAdmin(p: Person): p is Admin {
  return p.type === "admin";
}
TypeScript

すると、どこからでもこう書けます。

function printPermissions(person: Person) {
  if (isAdmin(person)) {
    // ここでは Admin 型に絞られる
    console.log(person.permissions);
  } else {
    console.log("権限はありません");
  }
}
TypeScript

メリットは3つあります。

1つの場所にだけ「Admin 判定のロジック」を書けばよくなる
if 文を読むだけで、「ここでは Admin に絞っているんだな」が一目で分かる
型ガードとして宣言しているので、TypeScript が型を自動で絞ってくれる

「よくやる型チェックを、意味のある名前の型ガードとして切り出す」
これが、型ガードを“設計の道具”として使いこなす第一歩です。

「この if を通ったら、変数はどの型のはず?」を言語化する

型ガードを使うかどうか悩んだら、こう自分に問いかけてください。

この条件が true のとき、この変数はどんな型のはず?
その“はず”を、TypeScript にも分かってもらいたい?

その答えを「value is 型」という形で書いたものが、型ガード関数です。

例えば、

「この if を通ったら person は Admin のはず」
person is Admin

「この if を通ったら value は string の配列のはず」
value is string[]

この“日本語の文”をそのまま戻り値の型にする感覚を持ってみてください。
それがそのまま、型ガードの設計になります。


まとめ:型ガードの本質は「人間の直感をコンパイラに伝える」こと

型ガードは、単なるテクニック集ではありません。
本質的には、こういう役割を持っています。

人間が
「この条件を満たしているなら、この変数はこの型だろう」
と思っているその感覚を、TypeScript に正確に教える仕組み

そのために、

  • typeof, instanceof, "prop" in obj, === null などの組み込みガード
  • value is 型 という形で書くユーザー定義型ガード

が用意されています。

コードを書いていて、

「ここでこうチェックしてるってことは、この中の変数はもうこの型のはずなんだけどなあ…」

と感じる場所があったら、そこが型ガードを使うポイントです。
その“はず”をちゃんと型として表現できたとき、
TypeScript はただの厳しい先生から、
「自分の意図を正確に理解してくれる共同開発者」 に変わっていきます。

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