TypeScriptの型ガードで起こりやすいエラーと対処法を徹底解説!初心者でも理解できる型安全の考え方
生徒
「TypeScriptの型ガードって便利と聞いたのですが、使っているとエラーが多くて不安になります。」
先生
「型ガードは、TypeScriptで型を安全に扱うための重要な仕組みですが、慣れていないとつまずきやすいポイントがあります。」
生徒
「具体的にどんなエラーが起こりやすいんですか?」
先生
「それでは、初心者がよく経験する型ガードのエラーと対処方法を、例を交えて分かりやすく学んでいきましょう。」
1. 型ガードとは?基本の理解から始めよう
TypeScriptの型ガードとは、変数の型を条件分岐によって絞り込み、特定の型として安全に扱えるようにする仕組みです。例えば、ある変数が文字列なのか数値なのかで処理を分けたい場合、型ガードを使うことでエラーを防ぎながら正しい処理を実行できます。
型ガードはユニオン型(複数の型を許可する型)を扱うときに特に重要です。例えば、値が入力フォームから取得される場合、文字列や数値として扱い方が変わるため、型の判定が欠かせません。
2. よくあるエラー:プロパティが存在しないと言われる
ユニオン型の値に対してプロパティへ直接アクセスすると、TypeScriptは安全性を確認できず、エラーが発生します。これは、含まれる型すべてがそのプロパティを持っているとは限らないためです。
type User = { name: string };
type Admin = { name: string; role: string };
function printRole(person: User | Admin) {
console.log(person.role); // エラー
}
上記の例では、User型にはroleが存在しないため、TypeScriptは安全なコードであると言えません。この問題を解決するには、型ガードを用いて型を判定します。
function printRole(person: User | Admin) {
if ("role" in person) {
console.log(person.role);
}
}
3. typeof で判定できる型とできない型を理解する
typeofは便利な型ガードですが、判定できる型には制限があります。たとえば、numberやstringやbooleanなど基本的な型は判定できますが、オブジェクトの中身を判定することはできません。
function checkValue(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase());
}
}
逆に、ユーザー定義型の場合にはtypeofは使えません。例えば、次のようなケースです。
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function speak(animal: Cat | Dog) {
if (typeof animal === "Cat") { // エラー
}
}
このような場合は、in演算子を使って判定します。
function speak(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow();
} else {
animal.bark();
}
}
4. カスタム型ガードの書き方を理解する
複雑な型を判定するためには、カスタム型ガードを使う方法があります。これは、自作の関数で型を判断し、その結果をTypeScriptに伝える仕組みです。
type Car = { drive: () => void };
type Bike = { ride: () => void };
function isCar(v: Car | Bike): v is Car {
return "drive" in v;
}
function move(v: Car | Bike) {
if (isCar(v)) {
v.drive();
} else {
v.ride();
}
}
このように型ガード関数を作ると、条件分岐後の型が確実に判定されているとTypeScriptが理解してくれます。
5. never 型のエラーの理解と対処法
型ガードを使っていると、neverという型が登場することがあります。これは、「絶対に起こらない型」を意味しており、型の分岐が完全に網羅されていないと発生します。
type Shape = { kind: "circle"; radius: number }
| { kind: "square"; size: number };
function area(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius * shape.radius;
case "square":
return shape.size * shape.size;
default:
const _exhaustive: never = shape; // エラー
return _exhaustive;
}
}
これは型の条件をすべて網羅することで解決できます。