まず「型ガードって何を守ってくれるのか」をイメージする
型ガード関数は一言でいうと、
「この値は〇〇型だよ、と TypeScript に“証拠付きで教える”ための関数」
です。
普通の if だと、TypeScript はあまり賢くなりません。
function fn(value: string | number) {
if (typeof value === "string") {
// ここでは value は string と推論される
console.log(value.toUpperCase());
}
}
TypeScripttypeof 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 ではありません");
}
}
TypeScriptif (isString(value)) の中に入った瞬間、value の型が string に絞り込まれます。
ここが型ガード関数の一番おいしいところです。
自作型ガードの設計ステップ
1. 「何を判定したいか」をはっきりさせる
まずは対象を決めます。
例えば、
unknownな値が「配列かどうか」- API レスポンスが「期待した形をしているか」
- ユニオン型の中で「どのバリアントか」
など。
例として、「Person 型かどうか」を判定する型ガードを作ってみます。
type Person = {
name: string;
age: number;
};
TypeScript2. 引数の型を決める
型ガードの引数は、だいたい unknown か広めの型にします。
function isPerson(value: unknown): value is Person {
// 実装はあとで
}
TypeScriptunknown にしておくと、
「何が来てもとりあえず受け取れる」ので、
外部から来たデータのチェックなどに使いやすくなります。
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 ではありません");
}
}
TypeScriptif (isPerson(value)) の中では、value が Person として扱えるようになっています。
「if の中で安全にプロパティにアクセスできるようにする」
これが型ガード関数の一番の役割です。
ユニオン型と型ガード:バリアントを見分ける
判別可能ユニオンと組み合わせる
次は、ユニオン型の中で「どの種類か」を判定する型ガードです。
type Circle = {
kind: "circle";
radius: number;
};
type Square = {
kind: "square";
size: number;
};
type Shape = Circle | Square;
TypeScriptShape が Circle か Square かを判定する型ガードを作ります。
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");
}
TypeScriptisCircle(shape) が true のブロックでは shape が Circle に、isSquare(shape) が true のブロックでは shape が Square に絞り込まれます。
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)); // 実行時に落ちるかもしれない
}
}
TypeScriptTypeScript は「型ガードが 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[]
TypeScriptArray.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[]
TypeScriptNonNullable<T> は「T から null と undefined を取り除いた型」です。
isNotNull は、
- 引数の型:
T(何でも) - 戻り値の型:
value is NonNullable<T>
という形で、
「null / undefined を取り除く型ガード」として設計されています。
こういう「汎用型ガード」を1つ持っておくと、
コードのあちこちで気持ちよく使えます。
実務的な設計の視点
どんなときに「型ガード関数」に切り出すべきか
こんな場面では、型ガード関数にする価値が高いです。
typeofやin、Array.isArrayなどのチェックが何度も出てくる- API レスポンスの形を毎回手でチェックしている
- ユニオン型のバリアント判定をあちこちで書いている
同じような「型チェックロジック」が2回以上出てきたら、
「これ、型ガード関数にできないか?」
と一度考えてみてください。
そうすると、
- 判定ロジックが1箇所にまとまる
- 呼び出し側の if 文が読みやすくなる
- TypeScript の絞り込みが効いて、補完も気持ちよくなる
という三拍子が揃います。
型ガード関数に名前をつけるセンス
型ガード関数は、名前も大事です。
isStringisPersonisCircleisPositiveNumberisNotNull
など、「is + 〇〇」の形にすると、
if 文の中で読んだときに自然な日本語になります。
if (isPerson(value)) {
// value は Person
}
TypeScript「if の条件を声に出して読んだときに自然か?」
を基準に名前をつけると、コード全体の読み心地がかなり良くなります。
まとめ:型ガード関数の設計を自分の言葉で言うと
最後に、あなた自身の言葉でこう整理してみてください。
型ガード関数は、
- 戻り値型に
value is 型と書くことで、
「true のときに引数の型を絞り込む」特別な関数。 unknownやユニオン型から、
「この条件を満たすなら〇〇型だ」と TypeScript に教えるための道具。- 判定ロジックを関数に閉じ込めることで、
if 文の中で安全にプロパティアクセスできるようにする。
設計するときは、
- 「何を判定したいか」をはっきりさせる
- true を返すときは、本当にその型の条件を満たしているように実装する
- 同じチェックが何度も出てきたら、型ガード関数に切り出す
このあたりを意識してみてください。
そうすると、型ガード関数は
「TypeScript に怒られないためのテクニック」から、
“型とロジックをきれいに結びつけるための設計ツール”
に変わっていきます。
