プログラムを書いているとき、関数や変数が複数の型を扱うことが求められる場面があります。TypeScriptでは、ユニオン型と呼ばれる機能を使って、変数や引数に複数の型を許容することができます。また、ユニオン型を使うときには、型ガードを使って安全に型を判別し、プログラムの実行時に型エラーが発生しないようにすることが重要です。
この章では、ユニオン型の基本から型ガードによる型の絞り込みの方法まで、詳細に説明していきます。
8.1 ユニオン型とは?
ユニオン型(Union Types)は、複数の型を持つことができる特殊な型です。例えば、ある変数がstring
型またはnumber
型のどちらかである可能性がある場合、ユニオン型を使ってその変数を定義できます。ユニオン型は、|
(パイプ)記号を使って表現します。
8.1.1 ユニオン型の定義
ユニオン型を使って変数を定義する際、複数の型を|
で区切ります。以下の例では、id
という変数がstring
型またはnumber
型である可能性を示しています。
let id: string | number;
id = "ABC123"; // OK: string型
id = 12345; // OK: number型
この例では、id
変数がstring
型でもnumber
型でも良いと定義されています。この柔軟性により、ユニオン型は異なる型のデータを扱う必要がある場面で非常に便利です。
8.1.2 関数のユニオン型引数
ユニオン型は関数の引数にも適用できます。次の例では、printId
という関数がstring
型またはnumber
型のid
を受け取ることができます。
function printId(id: string | number): void {
console.log(`ID: ${id}`);
}
printId(101); // "ID: 101"
printId("XYZ789"); // "ID: XYZ789"
この例のように、ユニオン型を使うことで、同じ関数が異なる型に対応する柔軟な設計が可能になります。
8.2 型ガードとは?
ユニオン型は複数の型を扱うことができる便利な機能ですが、プログラムの中で特定の型に対して異なる処理を行う場合には、型ガード(Type Guards)を使って型を判別する必要があります。型ガードは、変数や引数が特定の型であるかを確認し、その型に基づいて適切な処理を実行するための手法です。
8.2.1 型ガードの基本
型ガードの基本的な方法は、typeof
演算子を使って型を判別することです。typeof
は、変数の型を判定するための標準的なJavaScriptの機能で、TypeScriptでも使用可能です。
function printId(id: string | number): void {
if (typeof id === "string") {
// idがstring型の場合
console.log(`String ID: ${id.toUpperCase()}`);
} else {
// idがnumber型の場合
console.log(`Number ID: ${id.toFixed(2)}`);
}
}
printId(101); // "Number ID: 101.00"
printId("XYZ789"); // "String ID: XYZ789"
この例では、typeof
を使ってid
の型を判別し、文字列の場合と数値の場合で異なる処理を実行しています。typeof
によってTypeScriptは型が絞り込まれ、id.toUpperCase()
やid.toFixed(2)
のようなメソッドを安全に呼び出せるようになります。
8.3 型の絞り込み(Narrowing)
型の絞り込み(Narrowing)とは、ユニオン型で定義された変数の型を特定の型に限定していくプロセスを指します。型ガードを使って特定の型に絞り込むことで、TypeScriptはその型に固有のメソッドやプロパティにアクセスできるようになります。
8.3.1 typeof
による型の絞り込み
前述のように、typeof
は基本的な型絞り込みの手段です。特に、string
、number
、boolean
、symbol
といった基本的な型に対して有効です。
function isString(value: string | number): boolean {
return typeof value === "string";
}
console.log(isString("hello")); // true
console.log(isString(123)); // false
この例では、typeof
を使って引数value
がstring
型かどうかを判定しています。
8.3.2 instanceof
による型の絞り込み
クラスやオブジェクトに対して型の絞り込みを行う際には、instanceof
演算子を使います。instanceof
は、オブジェクトが特定のクラスのインスタンスであるかどうかを判定するために使用されます。
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function makeSound(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
let dog = new Dog();
let cat = new Cat();
makeSound(dog); // "Woof!"
makeSound(cat); // "Meow!"
この例では、instanceof
を使ってDog
またはCat
のインスタンスであるかどうかを判別し、それに応じて異なるメソッドを呼び出しています。
8.4 リテラル型とリテラルの絞り込み
TypeScriptでは、リテラル型を使って特定の値を型として扱うことができます。リテラル型とは、"hello"
や42
などの特定の値そのものを型として扱う機能です。ユニオン型と組み合わせて使うと、特定の値に基づいた型の絞り込みが可能になります。
8.4.1 リテラル型の基本
リテラル型は、固定された特定の値を型として指定します。次の例では、"success"
や"error"
といった文字列リテラル型を使って、特定の結果を表現しています。
type Status = "success" | "error";
function printStatus(status: Status): void {
if (status === "success") {
console.log("Operation was successful!");
} else {
console.log("Operation failed.");
}
}
printStatus("success"); // "Operation was successful!"
printStatus("error"); // "Operation failed."
この例では、Status
というユニオン型に2つのリテラル型"success"
と"error"
が定義されています。関数内でこれらのリテラル型に基づいて型を絞り込むことで、安全に処理を実行できます。
8.4.2 リテラル型の絞り込み
リテラル型の絞り込みは、単にリテラルの値を直接比較することで行えます。TypeScriptは条件によってリテラル型が絞り込まれるため、そのリテラルに基づいた安全な処理を行うことが可能です。
type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction): void {
switch (direction) {
case "up":
console.log("Moving up");
break;
case "down":
console.log("Moving down");
break;
case "left":
console.log("Moving left");
break;
case "right":
console.log("Moving right");
break;
}
}
move("up"); // "Moving up"
move("left"); // "Moving left"
この例では、Direction
というリテラル型が定義されており、4つの固定された値しか取ることができません。switch
文を使ってリテラルの値を比較し、適切な処理を行っています。
8.5 in
演算子を使った型ガード
in
演算子は、オブジェクトが特定のプロパティを持っているかどうかを確認するために使用されます。オブジェクトの構造に基づいて型を絞り込むために、in
を使った型ガードが有効です。
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
function moveAnimal(animal: Fish | Bird): void {
if ("swim" in animal) {
animal.swim();
} else {
animal.fly();
}
}
let fish: Fish = { swim: () => console.log("Swimming...") };
let bird: Bird = { fly: () => console.log("Flying...") };
moveAnimal(fish); // "Swimming..."
moveAnimal(bird); // "Flying..."
この例では、in
演算子を使ってanimal
オブジェクトがswim
プロパティを持っているかどうかを確認し、その結果に基づいて型を絞り込んでいます。これにより、Fish
とBird
のどちらかを安全に判別することができます。
8.6 never
型と型の絞り込み
TypeScriptのnever
型は、「絶対に起こり得ない型」を表します。通常、never
型はあり得ない状況に遭遇したときや、型の絞り込みが完全に行われたときに現れます。never
型を使うことで、コードの網羅性チェックを行うことができます。
8.6.1 never
型の例
never
型は、通常のプログラムの中ではほとんど直接使うことはありませんが、型安全性を確保するためのエラーチェックや、網羅性を確認する場合に役立ちます。
type Shape = "circle" | "square";
function getArea(shape: Shape): number {
switch (shape) {
case "circle":
return 3.14 * 5 * 5; // 半径5の円の面積
case "square":
return 5 * 5; // 一辺5の正方形の面積
default:
// 型絞り込みが漏れている場合、ここでエラーを出す
const exhaustiveCheck: never = shape;
throw new Error(`Unexpected shape: ${exhaustiveCheck}`);
}
}
この例では、Shape
型が"circle"
と"square"
しか取り得ないことを前提にしています。default
ケースでnever
型を使用することで、もし将来的にShape
に新しい値が追加された場合、TypeScriptはコンパイルエラーを発生させ、型絞り込みの漏れを検出します。
まとめ
この章では、TypeScriptのユニオン型と型ガードについて詳しく学びました。ユニオン型は、複数の型を柔軟に扱うための強力な機能であり、型ガードを使って特定の型に絞り込むことで、安全に異なる型のデータを操作することができます。また、typeof
やinstanceof
、in
演算子、リテラル型を使った絞り込みの方法や、never
型を活用した型安全性の向上方法も学びました。