「型ガード」とは何かを一言でつかむ
まずイメージからいきます。
型ガードは、
「この 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最初 value は string | number(どっちか)です。typeof value === "string" と書いた if の中では、
「string であることをチェックした」
→ 「ここでは string として扱っていい」
と TypeScript が理解し、toUpperCase や length が使えるようになります。
逆に 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());
}
TypeScriptname は最初 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 の中で person は Admin に、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;
}
}
TypeScriptshape.kind の値と、union の各型の kind が対応しているので、case "circle" に入った時点で shape は Circle に、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
TypeScriptvalue 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 ではありません");
}
}
TypeScriptisString(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);
}
}
TypeScriptif の中で 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 はただの厳しい先生から、
「自分の意図を正確に理解してくれる共同開発者」 に変わっていきます。
