ゴール:「ユーザー定義型ガード」を“自分で設計して使いこなせる”ようになる
ここで目指したいのは、
「ユーザー定義型ガードって何?」から一歩進んで、
「いつ・どう設計するとコードが楽になるか」までイメージできる状態です。
キーワードはこれです。
value is 型という特別な戻り値型unknownやユニオン型から「安全に絞り込む」- 同じ判定ロジックを“関数に閉じ込めて再利用する”
順番に、かみ砕いていきます。
ユーザー定義型ガードとは何か
「自分で作る typeof / Array.isArray みたいなもの」
TypeScript には、すでにいくつか「組み込みの型ガード」があります。
if (typeof value === "string") { /* value は string */ }
if (Array.isArray(value)) { /* value は any[] */ }
TypeScriptこれと同じように、
「この関数が true を返したら、引数は〇〇型だとみなしていい」
という“自作の判定関数”を作れる仕組みが
ユーザー定義型ガード(user-defined type guard)です。
そのための文法が、戻り値型の
value is 型
TypeScriptという書き方です。
まずは最小の例:isString
function isString(value: unknown): value is string {
return typeof value === "string";
}
TypeScriptここで重要なのは 2 つです。
- 引数の型:
unknown(何が来るか分からない) - 戻り値の型:
value is string(true のとき string とみなしてよい)
この関数を使うと、こうなります。
function printUpper(value: unknown) {
if (isString(value)) {
// ここでは value は string として扱える
console.log(value.toUpperCase());
} else {
console.log("string ではありません");
}
}
TypeScriptif (isString(value)) の中に入った瞬間、
TypeScript は value の型を string に絞り込んでくれます。
ここが、ユーザー定義型ガードの一番の威力です。
基本形の設計:unknown から安全に絞り込む
ステップ1:判定したい型を決める
例として、Person 型を定義します。
type Person = {
name: string;
age: number;
};
TypeScript外部から来たデータ(JSON など)が Person かどうかを判定したい、
という状況をイメージしてください。
ステップ2:引数は広く(だいたい unknown)
外から来るものは何でも受け取れるように、
引数の型は unknown にするのが定番です。
function isPerson(value: unknown): value is Person {
// 実装はあとで
}
TypeScriptany ではなく unknown にするのは、
「何も分からないから、ちゃんとチェックしよう」という姿勢を
型で表現するためです。
ステップ3:中身を JavaScript 的にチェックする
Person である条件を、普通の JS で書きます。
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 は value is Person を「信じるしかない」ので、
ここで嘘をつくと、型安全性が崩れます。
ステップ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 として扱えるようになっています。
「unknown → Person への安全な橋渡し」を
関数に閉じ込めた、というイメージです。
ユニオン型とユーザー定義型ガード
判別可能ユニオンを“関数で判定する”
次は、ユニオン型の中で「どの種類か」を判定するパターンです。
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使うとどう絞り込まれるか
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) でも同じことはできますが、
ユーザー定義型ガードにすると、
- 判定ロジックを関数として再利用できる
- if 文の条件が読みやすくなる(
if (isCircle(shape))は直感的)
というメリットがあります。
配列と相性が良すぎる型ガード
filter に型ガード関数を渡すとどうなるか
ユーザー定義型ガードは、Array.prototype.filter と組み合わせると真価を発揮します。
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[]
TypeScriptfilter は「型ガード関数が渡されたとき、
戻り値の配列の要素型を絞り込む」という仕様になっています。
つまり、
「配列から“欲しい型だけ”を取り出すとき、
ユーザー定義型ガードを 1 個用意しておくと、
戻り値の型がきれいに狭まる」
ということです。
null / undefined を取り除く汎用型ガード
よく使うパターンをもう 1 つ。
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>
という形の、ジェネリックなユーザー定義型ガードです。
1 回書いておくと、
「null / undefined を取り除きたい配列」で何度でも使えます。
重要な注意点:「true のときの約束」は絶対に守る
型ガードは TypeScript にとって“信頼するしかない宣言”
戻り値型 value is T は、
「この関数が true を返したとき、value は 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 を返す」のは安全です。
function isPositiveNumber(value: unknown): value is number {
return typeof value === "number" && value > 0;
}
TypeScripttrue のときは「正の number」であることが保証されます。
false のときは「正の number ではない」だけなので、
多少広くても問題ありません。
実務的な設計の視点
どんなときに「ユーザー定義型ガード」に切り出すべきか
こんな状況が出てきたら、型ガードを検討するタイミングです。
typeof,in,Array.isArrayなどのチェックが何度も出てくる- API レスポンスの形を毎回手でチェックしている
- ユニオン型のバリアント判定をあちこちで書いている
同じような判定ロジックが 2 回以上出てきたら、
「これ、ユーザー定義型ガードにできないか?」
と一度立ち止まってみてください。
そうすると、
- 判定ロジックが 1 箇所にまとまる
- if 文の条件が読みやすくなる(
if (isPerson(value))など) - TypeScript の絞り込みが効いて、補完も気持ちよくなる
という良い循環が生まれます。
名前の付け方も“設計”の一部
型ガード関数は、名前もかなり大事です。
isStringisPersonisCircleisPositiveNumberisNotNull
など、「is + 〇〇」の形にすると、
if 文を声に出して読んだときに自然になります。
if (isPerson(value)) {
// value は Person
}
TypeScript「if の条件が、そのまま日本語の文章として読めるか?」
を基準に名前をつけると、
コード全体の読み心地が一気に良くなります。
まとめ:ユーザー定義型ガードを自分の言葉で言うと
最後に、あなた自身の言葉でこう整理してみてください。
ユーザー定義型ガードは、
- 戻り値型に
value is 型と書くことで、
「true のときに引数の型を絞り込む」特別な関数。 unknownやユニオン型から、
「この条件を満たすなら〇〇型だ」と TypeScript に教えるための仕組み。- 判定ロジックを関数に閉じ込めることで、
if 文の中で安全にプロパティアクセスできるようにする。
設計するときは、
- 何を判定したいかをはっきりさせる
- 引数は広め(多くは
unknown)に取る - true のときに本当にその型の条件を満たすように実装する
- 同じチェックが何度も出てきたら、型ガードに切り出す
このあたりを意識してみてください。
そうすると、ユーザー定義型ガードは
「TypeScript の小技」ではなく、
“型とロジックをきれいに結びつけるための設計ツール”
として、あなたのコードを一段引き上げてくれます。

