【入門編】 ユーザー定義型ガード (isキーワード) – TypeScript実践ガイド

TypeScriptでの開発に少し慣れてくると、誰もが一度はこんな壁にぶつかります。

「あれ、このデータは文字列なのか数字なのか、さっき自分でif文でチェックしたはずなのに、TypeScriptくんがわかってくれない……!」

APIから返ってきたデータや、ユーザーが入力したフォームの値など、現実の世界は「もしかしたらこれかもしれないし、あれかもしれない」という曖昧さに満ちています。そんなとき、TypeScriptの型チェック機能と私たちの意図がすれ違って、赤い波線(エラー)に怒られてしまう。

今回は、そんなモヤモヤを綺麗に解消してくれる、TypeScriptのちょっと魔法のような機能「ユーザー定義型ガード(`is`キーワード)」について、一緒にゆっくり紐解いていきましょう。

大丈夫です、一歩ずつ見ていけば、あなたのコードがぐっとエレガントになる最高の相棒になってくれますよ。

—

そもそも「型ガード」ってなぁに?(お買い物の例え)

TypeScriptの `unknown` 型や、複数の型をまとめた「union型(例: `string | number`)」を扱うとき、私たちはよくこんな風にコードを書きます。

function printId(id: string | number) {
// ここで「もし文字列だったら…」とチェックしたい!
if (typeof id === “string”) {
console.log(id.toUpperCase()); // TypeScript「あ、ここなら文字列だね、OK!」
}
}

この `typeof id === “string”` のように、「この中身、実はこういう型なんだよ」とTypeScriptに教えてあげる仕組みを「型ガード」と呼びます。JavaScriptやTypeScriptが標準で持っている `typeof` や `instanceof` は、言わば「お墨付きの自動チェッカー」です。

しかし、世の中にはもっと複雑なデータ構造がありますよね。例えば、次のような「ECサイトの会員データ」を考えてみましょう。

  • 一般会員:名前と「ポイント数」を持っている
  • プレミアム会員:名前と「年間購入金額」と「専属担当者の名前」を持っている

「このデータは、一般会員なのか、それともプレミアム会員なのか?」を判定する専用のチェック機能を作りたくなったとき、標準の `typeof` だけでは太刀打ちできなくなります。

ここで登場するのが、今回の主役であるユーザー定義型ガード(`is`キーワード)です。

—

魔法のキーワード `arg is Type` の正体

ユーザー定義型ガードは、一言で言うと「私たちが自作する、TypeScript専用の『型のお墨付きハンコ』」です。

百聞は一見に如かず。まずはコードを見てみましょう。

// 一般会員の型
type NormalUser = {
name: string;
points: number;
};

// プレミアム会員の型
type PremiumUser = {
name: string;
annualSpent: number;
conciergeName: string;
};

// どんなデータが入っているかわからない状態(unknownなど)
type User = NormalUser | PremiumUser;

ここに、「このユーザーはプレミアム会員かどうか」を判定する関数を作ります。

// ─── ここがユーザー定義型ガードの関数です! ───
// 戻り値のところに注目。「user is PremiumUser」と書いています。
function isPremiumUser(user: User): user is PremiumUser {
// 渡されたデータの中に「conciergeName(専属担当者)」というプロパティが含まれているかで判断する
return “conciergeName” in user;
}

この関数の中に書かれている `user is PremiumUser` という部分が、TypeScriptへの「お告げ」になります。

「この関数が `true` を返したということは、その中にいる `user` は、間違いなく `PremiumUser` だからね! 後続の処理ではそのつもりでよろしく頼むよ!」と、TypeScriptの頭脳を書き換えてくれるのです。

—

実際に動かして、その便利さを体感してみよう

では、先ほど作った「型ガード関数」を実際の処理の中で使ってみましょう。

function handleUser(user: User) {
// ここではまだ user は NormalUser か PremiumUser か曖昧な状態

if (isPremiumUser(user)) {
// 💡 このブロックの中に入ってきた瞬間、
// TypeScriptは「おっ、さっきの関数が true を返したから、この人はプレミアム会員だな!」と理解する

console.log(`ようこそ、プレミアム会員の ${user.name} 様!`);
console.log(`専属担当の ${user.conciergeName} がサポートいたします。`); // エラーにならない!

} else {
// こっちのブロックでは、自動的に NormalUser として扱われる
console.log(`こんにちは、${user.name} さん。現在のポイントは ${user.points}pt です。`);
}
}

もし、この `is` キーワードを使わずに自作の関数で `true / false` だけを返していたらどうなるでしょうか? TypeScriptは「ただの真偽値(boolean)が返ってきたな」としか思わないため、if文の中に入っても `user.conciergeName` にアクセスしようとすると、「そんなプロパティあるかどうかわからないよ!」と赤色のエラーで怒られてしまいます。

この「判定ロジックを関数に切り出しつつ、TypeScriptの型推論も連動させる」という技ができるだけで、コードの美しさと読みやすさが段違いに跳ね上がります。

—

初学者がつまずきやすいポイントと「大丈夫だよ」のフォロー

型ガードを書き始めると、初心者のうちは何度かこんな罠にハマることがあります。

1. `is` の前の引数名が一致していない

// ❌ 間違い例
function isString(value: unknown): target is string {
// 引数は ‘value’ なのに、右側で ‘target is’ と別の名前を書いている
return typeof value === “string”;
}

対策: 左側と右側の名前は必ず同じにしてください(`value is string`)。TypeScriptは「この引数が、この型なんだよ」と紐づけているため、名前がズレると混乱してしまいます。

2. 中身の判定ロジックが間違っている

`is` キーワードは、あくまで「開発者がTypeScriptをだます(あるいは正しい保証を与える)ための免許証」のようなものです。極端な話、中身の判定がデタラメでも、TypeScriptは「あなたがそう言うなら信じるよ」と通してしまいます。

// 嘘をつく型ガード(絶対ダメ!)
function isNumber(val: unknown): val is number {
return typeof val === “string”; // 文字列なのに number だと言い張っている
}

対策: `is` キーワードを使うときは、その関数の中身の判定(`typeof` チェックやプロパティの有無など)が本当に正しいかを、自分の目でしっかり確認するようにしましょう。型ガードの中身こそ、堅実なJavaScriptのロジックが求められます。

—

まとめ:複雑な世界を、優しく型安全に包み込もう

今回は、TypeScriptのちょっとディープで最高に便利な機能「ユーザー定義型ガード(`is` キーワード)」について見てきました。

  • 何をするもの?:複雑な条件判定の裏で、「このデータはこういう型なんだよ」とTypeScriptに教えてあげるための自作の目印。
  • どう書くの?:関数の戻り値に `引数名 is 期待する型` と指定する。
  • どんなときに使うの?:APIからのレスポンス、union型が入り乱れる複雑なフォームデータ、ライブラリ独自のデータ構造をすっきり整理したいとき。

最初は「なんか呪文みたいな書き方だな……」と感じるかもしれませんが、一度この快適さを知ると、もう普通のif文だけでは物足りなくなってしまうはずです。

ご自身のプロジェクトで「あ、ここ型が絞り込めなくて困ったな」という場面に出くわしたら、ぜひこの `is` キーワードを思い出してみてくださいね。あなたのTypeScriptライフが、より安全で、心地よいものになりますように!

コメント

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