【入門編】Union型を引数に取る関数での「型ガード」の効率的な実装 – TypeScript コア・型システムの基礎解析バイブル

TypeScriptの「型ガード」を極める:Union型を安全に使いこなすための思考法

こんにちは。TypeScriptの世界へようこそ。

TypeScriptを触り始めると、誰もが一度は「この変数は `string` かもしれないし、`number` かもしれない。どうやって安全に扱えばいいの?」という壁にぶつかりますよね。

いわゆる「Union型(`string | number`)」を引数に取る関数を実装する際、場当たり的な型アサーション(`as`)で逃げていませんか? それは、TypeScriptが提供する「型推論の魔法」を自ら捨てているのと同じです。

今日は、「ユーザー定義型ガード(User-Defined Type Guards)」という武器を使って、コードの安全性を高めつつ、コンパイラの力を最大限に引き出す方法を伝授します。ここをクリアすれば、あなたの書くコードは驚くほど堅牢になりますよ。

—

1. なぜ「そのまま」ではいけないのか?

まずは、避けるべき「よくある書き方」を見てみましょう。

type User = { name: string };
type Admin = { name: string; role: string };

function sayHello(person: User | Admin) {
// コンパイルエラー!
// TypeScriptは、personがAdmin型かどうかを知らないので、
// 安全のためにプロパティへのアクセスを制限します。
console.log(person.role);
}

コンパイラは「`person` には `role` があるかもしれないし、ないかもしれない」と判断します。ここで安易に `(person as Admin).role` と書くのは、「俺が責任を持つから黙ってろ!」とコンパイラを黙らせる行為です。これではTypeScriptを使う意味がありませんよね。

—

2. ユーザー定義型ガード:コンパイラに「証明」を教える

TypeScriptには、コンパイラに対して「この関数が `true` を返したら、この変数はこの型だよ」と教える特別な書き方があります。これが「ユーザー定義型ガード」です。

実装のルール:型述語(Type Predicate)

戻り値の型部分に `引数名 is 型名` と書くのがポイントです。

// 型ガード関数
function isAdmin(person: User | Admin): person is Admin {
// ‘role’ というプロパティを持っているかで判定
return (person as Admin).role !== undefined;
}

function processUser(person: User | Admin) {
if (isAdmin(person)) {
// ここでは person は自動的に Admin 型として扱われる!
console.log(`管理者の名前は ${person.name}、権限は ${person.role} です。`);
} else {
// ここでは person は User 型として扱われる
console.log(`一般ユーザーの ${person.name} さん、こんにちは。`);
}
}

ここが重要です:
`isAdmin` 関数の中で `person as Admin` を使っていますが、これは「内部的なガード」として許容されます。外側の `processUser` 関数では、もはや型アサーションは不要です。コンパイラが「`isAdmin` が `true` を返したなら、`person` は `Admin` だ」と論理的に理解してくれるからです。

—

3. 現場で役立つ「判別可能なUnion型」という選択肢

もし、あなたの型定義を自分でコントロールできるなら、もっとスマートな方法があります。それが「判別可能なUnion型(Discriminated Unions)」です。

型の中に「共通の識別子(タグ)」を持たせる手法で、現代TypeScript開発のデファクトスタンダードです。

type User = { type: ‘user’; name: string };
type Admin = { type: ‘admin’; name: string; role: string };

type Person = User | Admin;

function greet(person: Person) {
// switch文やif文で ‘type’ を見れば、自動的に型が絞り込まれる(型ナローイング)
switch (person.type) {
case ‘admin’:
console.log(`管理者: ${person.role}`); // 型がAdminに確定
break;
case ‘user’:
console.log(`一般ユーザー: ${person.name}`); // 型がUserに確定
break;
}
}

これは、ユーザー定義型ガードすら書かなくて良い最も安全で効率的なパターンです。「型ガードを書く必要すらない設計にする」のが、実は一番の近道なのです。

—

4. 初学者が陥りやすいミスと心構え

最後に、現場でよく見る「やってしまいがちな罠」を共有します。

  • 罠1:`typeof` で無理やり判定しようとする
  • `typeof` は `string` や `number` などのプリミティブ型には有効ですが、オブジェクトの構造を判定することはできません。カスタム型には必ず `in` 演算子や、上記のような型ガード関数を使いましょう。
  • 罠2:型ガードの戻り値を `boolean` にしてしまう
  • `function isUser(p: any): boolean` と書くと、TypeScriptは型ガードとして認識してくれません。必ず `p is User` と型述語を明示してください。

—

まとめ:TypeScriptは「対話」である

TypeScriptのコンパイラは、あなたの敵ではなく、「型という言語で議論してくれるパートナー」です。

1. まずは「判別可能なUnion型」で設計できないか考える。
2. 既存のAPIなどで難しい場合は「型述語(`is`)」を使って明示的に教えてあげる。
3. `as` アサーションは最終手段として封印する。

このステップを踏むだけで、あなたのコードの安全性は劇的に向上します。最初は少し手間だと感じるかもしれませんが、その「手間」が未来の自分をバグから救ってくれる「保険」になるんです。

さあ、自信を持って型ガードを書いていきましょう。あなたのコードは、もっと安全になれるはずですよ!

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