TypeScript | 関数・クラス・ジェネリクス:関数設計の深化 – 型ガード関数の設計

TypeScript
スポンサーリンク

まず「型ガードって何を守ってくれるのか」をイメージする

型ガード関数は一言でいうと、

「この値は〇〇型だよ、と TypeScript に“証拠付きで教える”ための関数」

です。

普通の if だと、TypeScript はあまり賢くなりません。

function fn(value: string | number) {
  if (typeof value === "string") {
    // ここでは value は string と推論される
    console.log(value.toUpperCase());
  }
}
TypeScript

typeof value === "string" という条件は、
TypeScript が特別扱いしてくれる「組み込みの型ガード」です。

これと同じことを、
「自分で定義した関数でできるようにする」のが“型ガード関数” です。


基本形:value is 型 という戻り値型を書く

型ガード関数のシグネチャの形

型ガード関数の一番の特徴は、戻り値の型です。

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

ここで重要なのは value is string という部分。

これは、

「この関数が true を返したとき、
引数 value は string 型だとみなしていい」

という“約束”を TypeScript に伝える型です。

普通の boolean 戻り値ではなく、
「boolean だけど、true のときに型が絞れる特別な boolean」
だと思ってください。

実際の使い方

function printUpper(value: unknown) {
  if (isString(value)) {
    // ここでは value は string として扱える
    console.log(value.toUpperCase());
  } else {
    console.log("string ではありません");
  }
}
TypeScript

if (isString(value)) の中に入った瞬間、
value の型が string に絞り込まれます。

ここが型ガード関数の一番おいしいところです。


自作型ガードの設計ステップ

1. 「何を判定したいか」をはっきりさせる

まずは対象を決めます。

例えば、

  • unknown な値が「配列かどうか」
  • API レスポンスが「期待した形をしているか」
  • ユニオン型の中で「どのバリアントか」

など。

例として、「Person 型かどうか」を判定する型ガードを作ってみます。

type Person = {
  name: string;
  age: number;
};
TypeScript

2. 引数の型を決める

型ガードの引数は、だいたい unknown か広めの型にします。

function isPerson(value: unknown): value is Person {
  // 実装はあとで
}
TypeScript

unknown にしておくと、
「何が来てもとりあえず受け取れる」ので、
外部から来たデータのチェックなどに使いやすくなります。

3. 中身のチェックを書く

Person である条件を、JavaScript 的に書きます。

function isPerson(value: unknown): value is Person {
  if (typeof value !== "object" || value === null) {
    return false;
  }

  const v = value as { [key: string]: unknown };

  return (
    typeof v.name === "string" &&
    typeof v.age === "number"
  );
}
TypeScript

ここで大事なのは、

「true を返すときは、本当に Person の条件を満たしていること」

です。

型ガードは TypeScript にとって「信頼するしかない約束」なので、
ここで嘘をつくと、型安全性が崩れます。

4. 実際に使ってみる

function greet(value: unknown) {
  if (isPerson(value)) {
    // ここでは value は Person 型
    console.log(`Hello, ${value.name} (${value.age})`);
  } else {
    console.log("Person ではありません");
  }
}
TypeScript

if (isPerson(value)) の中では、
valuePerson として扱えるようになっています。

「if の中で安全にプロパティにアクセスできるようにする」
これが型ガード関数の一番の役割です。


ユニオン型と型ガード:バリアントを見分ける

判別可能ユニオンと組み合わせる

次は、ユニオン型の中で「どの種類か」を判定する型ガードです。

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

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

type Shape = Circle | Square;
TypeScript

ShapeCircleSquare かを判定する型ガードを作ります。

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

function isSquare(shape: Shape): shape is Square {
  return shape.kind === "square";
}
TypeScript

ここでのポイントは、

  • 引数の型:Shape(ユニオン)
  • 戻り値の型:shape is Circle / shape is Square

という対応です。

使うとどうなるか

function area(shape: Shape): number {
  if (isCircle(shape)) {
    // ここでは shape は Circle
    return Math.PI * shape.radius * shape.radius;
  }

  if (isSquare(shape)) {
    // ここでは shape は Square
    return shape.size * shape.size;
  }

  // ここには来ないはず
  throw new Error("Unknown shape");
}
TypeScript

isCircle(shape) が true のブロックでは shapeCircle に、
isSquare(shape) が true のブロックでは shapeSquare に絞り込まれます。

switch (shape.kind) でも同じことはできますが、
「判定ロジックを関数として切り出して再利用できる」
というのが型ガード関数の強みです。


重要ポイント:型ガード関数は「真のときだけ」保証する

value is T は「true のときの約束」

型ガードの戻り値 value is T は、

「この関数が true を返したとき、value は T だとみなしてよい」

という意味です。

逆に言うと、

  • false のときに value が T ではないこと
  • true のときに value が T であること

両方を完璧に保証する必要はありませんが、
少なくとも「true のときに T である」ことは守るべきです。

例えば、これはダメです。

function isNumberBad(value: unknown): value is number {
  return true; // 何でも true にしてしまう
}
TypeScript

これを使うと、

function f(value: unknown) {
  if (isNumberBad(value)) {
    // ここでは value は number とみなされる
    console.log(value.toFixed(2)); // 実行時に落ちるかもしれない
  }
}
TypeScript

TypeScript は「型ガードが true を返したなら number だろう」と信じてくれます。
だからこそ、実装側が嘘をつかないことが超重要です。

「厳しめに false を返す」のは安全

逆に、少し厳しめに判定して false を返すのは安全です。

function isPositiveNumber(value: unknown): value is number {
  return typeof value === "number" && value > 0;
}
TypeScript

これは、

  • true のとき:value は「正の number」
  • false のとき:value は「正の number ではない(0 や負数や number 以外)」

という意味になります。

「true のときに T である」さえ守っていれば、
false 側は多少広くても狭くても問題ありません。


ジェネリクスと型ガード:少しだけ応用

配列の要素を絞り込む型ガード

例えば、「配列の中から number だけを取り出したい」とします。

function isNumber(value: unknown): value is number {
  return typeof value === "number";
}

const values: unknown[] = [1, "a", 2, null, 3];

const numbers = values.filter(isNumber);
// numbers: number[]
TypeScript

Array.prototype.filter は、
「型ガード関数を渡すと、戻り値の配列の要素型を絞り込んでくれる」
という仕様になっています。

ここでのポイントは、

「型ガード関数は、配列のフィルタリングと相性がめちゃくちゃ良い」

ということです。

ジェネリックな型ガードも書ける

もう少しだけ踏み込むと、
ジェネリクスを使った型ガードも書けます。

function isNotNull<T>(value: T): value is NonNullable<T> {
  return value !== null && value !== undefined;
}

const arr = [1, null, 2, undefined, 3];

const filtered = arr.filter(isNotNull);
// filtered: number[]
TypeScript

NonNullable<T> は「T から null と undefined を取り除いた型」です。

isNotNull は、

  • 引数の型:T(何でも)
  • 戻り値の型:value is NonNullable<T>

という形で、
「null / undefined を取り除く型ガード」として設計されています。

こういう「汎用型ガード」を1つ持っておくと、
コードのあちこちで気持ちよく使えます。


実務的な設計の視点

どんなときに「型ガード関数」に切り出すべきか

こんな場面では、型ガード関数にする価値が高いです。

  • typeofinArray.isArray などのチェックが何度も出てくる
  • API レスポンスの形を毎回手でチェックしている
  • ユニオン型のバリアント判定をあちこちで書いている

同じような「型チェックロジック」が2回以上出てきたら、
「これ、型ガード関数にできないか?」
と一度考えてみてください。

そうすると、

  • 判定ロジックが1箇所にまとまる
  • 呼び出し側の if 文が読みやすくなる
  • TypeScript の絞り込みが効いて、補完も気持ちよくなる

という三拍子が揃います。

型ガード関数に名前をつけるセンス

型ガード関数は、名前も大事です。

  • isString
  • isPerson
  • isCircle
  • isPositiveNumber
  • isNotNull

など、「is + 〇〇」の形にすると、
if 文の中で読んだときに自然な日本語になります。

if (isPerson(value)) {
  // value は Person
}
TypeScript

「if の条件を声に出して読んだときに自然か?」
を基準に名前をつけると、コード全体の読み心地がかなり良くなります。


まとめ:型ガード関数の設計を自分の言葉で言うと

最後に、あなた自身の言葉でこう整理してみてください。

型ガード関数は、

  • 戻り値型に value is 型 と書くことで、
    「true のときに引数の型を絞り込む」特別な関数。
  • unknown やユニオン型から、
    「この条件を満たすなら〇〇型だ」と TypeScript に教えるための道具。
  • 判定ロジックを関数に閉じ込めることで、
    if 文の中で安全にプロパティアクセスできるようにする。

設計するときは、

  • 「何を判定したいか」をはっきりさせる
  • true を返すときは、本当にその型の条件を満たしているように実装する
  • 同じチェックが何度も出てきたら、型ガード関数に切り出す

このあたりを意識してみてください。

そうすると、型ガード関数は
「TypeScript に怒られないためのテクニック」から、
“型とロジックをきれいに結びつけるための設計ツール”

に変わっていきます。

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