型ガードの書き方と落とし穴
型ガードは、実行時の値チェックを型システムに伝える仕組みだ。unknown や ユニオン型を安全に扱うために欠かせないが、書き方を間違えると型安全性が崩壊する。正しいパターンと落とし穴を押さえておこう。
組み込みの型ガード
TypeScript は typeof、instanceof、in などの演算子を型ガードとして認識する。
function process(value: string | number) {
if (typeof value === "string") {
// value は string 型に絞り込まれる
console.log(value.toUpperCase());
} else {
// value は number 型に絞り込まれる
console.log(value.toFixed(2));
}
}instanceof はクラスのインスタンスを判定する。
function handleError(error: unknown) {
if (error instanceof Error) {
console.log(error.message);
} else {
console.log(String(error));
}
}in 演算子はプロパティの存在をチェックする。
type Dog = { bark: () => void };
type Cat = { meow: () => void };
function speak(animal: Dog | Cat) {
if ("bark" in animal) {
animal.bark();
} else {
animal.meow();
}
}ユーザー定義型ガード
組み込みの型ガードでは対応できない場合、自分で型ガード関数を書く。
interface User {
name: string;
email: string;
}
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
"email" in value &&
typeof (value as User).name === "string" &&
typeof (value as User).email === "string"
);
}戻り値の型 value is User がポイントだ。これを「型述語(type predicate)」と呼ぶ。関数が true を返したとき、引数の型が User に絞り込まれることを TypeScript に伝えている。
const data: unknown = JSON.parse('{"name": "Alice", "email": "alice@example.com"}');
if (isUser(data)) {
// data は User 型として扱える
console.log(data.name);
}落とし穴:型ガードの嘘
型ガード関数は嘘をつける。TypeScript は関数の中身を検証しない。
// 危険な型ガード
function isUser(value: unknown): value is User {
return true; // 常に true を返す
}
const data: unknown = { foo: "bar" };
if (isUser(data)) {
// コンパイルは通るが、実行時にエラー
console.log(data.email.toUpperCase());
}実行時チェックと型述語が一致している。安全。
実行時チェックが不十分なのに型述語だけ書いている。実行時エラーの原因。
落とし穴:プロパティの型チェック漏れ
プロパティの「存在」だけでなく「型」もチェックしないと危険だ。
// 不十分な型ガード
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
"email" in value
// name と email が string かどうかチェックしていない
);
}
const data = { name: 123, email: null };
if (isUser(data)) {
// data.name は number、data.email は null
console.log(data.name.toUpperCase()); // 実行時エラー
}asserts を使った型ガード
TypeScript 3.7 以降、asserts キーワードでアサーション関数を書ける。
function assertIsUser(value: unknown): asserts value is User {
if (
typeof value !== "object" ||
value === null ||
!("name" in value) ||
!("email" in value)
) {
throw new Error("Not a User");
}
}
const data: unknown = fetchData();
assertIsUser(data);
// ここ以降、data は User 型
console.log(data.name);asserts 関数は戻り値を返さず、検証に失敗したら例外を投げる。これにより if 文なしで型を絞り込める。
実践的なパターン
型ガードを手書きするのは面倒でミスしやすい。スキーマ検証ライブラリを使えば、型定義と実行時チェックを一元管理できる。
filter メソッドに型ガードを渡すと、配列の型も絞り込まれる。(arr as User[]).filter(...) のようなキャストを避けられる。
const items: (User | null)[] = [user1, null, user2];
// null を除外し、型も User[] になる
const users = items.filter((item): item is User => item !== null);型ガードは TypeScript の型安全性を支える重要な機能だが、責任は開発者にある。型述語が嘘をつかないよう、実行時チェックは慎重に書こう。