TypeScriptの型システムは非常に強力で、複雑なアプリケーションの構築において大きな助けとなります。基本的な型指定に加えて、TypeScriptには複雑な型を扱うための機能が豊富に用意されています。この記事では、ユニオン型や交差型、条件付き型、型ガードなどの高度な型システムについて解説します。
1. ユニオン型(Union Types)
ユニオン型は、複数の型のうち、いずれかの型であれば許容するという柔軟な型指定ができます。|
を使って、複数の型を結合します。
let value: string | number;
value = "Hello"; // OK
value = 42; // OK
// value = true; // エラー: 'boolean'は許可されていない型
このように、value
にはstring
またはnumber
のいずれかを代入できることが保証されます。ユニオン型は、異なる型のデータを処理する際に非常に便利です。
ユニオン型の活用例
ユニオン型は、関数の引数や戻り値の型を柔軟に指定する際に役立ちます。
function printId(id: string | number): void {
console.log(`ID: ${id}`);
}
printId(101); // OK
printId("A123"); // OK
この関数は、string
型かnumber
型のid
を受け取ることができ、どちらの型でも同じ処理を行います。
2. 交差型(Intersection Types)
交差型は、複数の型を組み合わせて、新しい型を作成するために使います。&
を使って複数の型を結合し、すべての型のプロパティを持つ型を定義できます。
interface Person {
name: string;
}
interface Employee {
employeeId: number;
}
type Worker = Person & Employee;
const worker: Worker = {
name: "Alice",
employeeId: 123
};
この例では、Worker
型はPerson
とEmployee
の両方のプロパティを持つことが必要です。交差型は、複数の型を統合して、新しい型を定義したい場合に役立ちます。
3. 型ガード(Type Guards)
ユニオン型を使う場合、実際に使われる値の型を安全に確認する必要があります。型ガードを使用することで、特定の型に応じた処理を実行できます。typeof
やinstanceof
を使って型を判定し、適切な処理を行います。
typeof
を使った型ガード
typeof
は、基本的な型チェックに使用できます。
function printValue(value: string | number): void {
if (typeof value === "string") {
console.log(`文字列: ${value}`);
} else {
console.log(`数値: ${value}`);
}
}
printValue("Hello"); // "文字列: Hello"
printValue(42); // "数値: 42"
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();
}
}
const dog = new Dog();
const cat = new Cat();
makeSound(dog); // "Woof!"
makeSound(cat); // "Meow!"
4. リテラル型(Literal Types)
リテラル型を使うと、変数が特定のリテラル値のみを持つことを指定できます。これにより、文字列や数値の特定の値を型として扱うことができます。
let direction: "up" | "down";
direction = "up"; // OK
// direction = "left"; // エラー: 'left'は型に含まれていない
リテラル型は、特定のオプションの中から選択させるような場合に便利です。
リテラル型とユニオン型の組み合わせ
リテラル型をユニオン型と組み合わせることで、関数に対して特定の選択肢を与えることができます。
function move(direction: "up" | "down" | "left" | "right"): void {
console.log(`Moving ${direction}`);
}
move("up"); // OK
move("left"); // OK
// move("forward"); // エラー: 'forward'は許可されていない
5. 条件付き型(Conditional Types)
条件付き型は、ある型が別の型に割り当て可能かどうかによって型を決定する、高度な型です。条件式は、次のように三項演算子のような形式で記述します。
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
この例では、T
がstring
に割り当て可能であればtrue
を、そうでなければfalse
を返す条件付き型を定義しています。
条件付き型の実用例
次に、条件付き型を使って、特定のプロパティを持つかどうかをチェックする型を定義します。
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
type HasSound<T> = T extends { bark(): void } ? "Dog" : "Cat";
type A = HasSound<Dog>; // "Dog"
type B = HasSound<Cat>; // "Cat"
この例では、オブジェクトがbark
メソッドを持つかどうかに応じて、"Dog"
か"Cat"
を返す型を定義しています。
6. Mapped Types(マッピング型)
マッピング型を使うと、既存の型をもとに新しい型を動的に作成できます。マッピング型は、keyof
と組み合わせて、オブジェクト型のプロパティを動的に変換します。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
const john: Readonly<Person> = {
name: "John",
age: 30
};
// john.name = "Jane"; // エラー: 'name'は読み取り専用プロパティ
この例では、Readonly<T>
は任意の型T
のすべてのプロパティを読み取り専用に変換しています。
Partial型
TypeScriptには、部分的なオブジェクトを定義するためのPartial<T>
という便利なマッピング型も用意されています。
interface User {
name: string;
age: number;
}
function updateUser(user: Partial<User>): void {
// ユーザー情報を部分的に更新する処理
}
updateUser({ name: "Alice" }); // OK
Partial<T>
は、すべてのプロパティがオプショナルになるように変換します。これにより、部分的にオブジェクトを更新する機能を実装できます。
まとめ
TypeScriptの高度な型システムを活用することで、より柔軟で堅牢なコードを記述することができます。ユニオン型や交差型、条件付き型、マッピング型などを組み合わせることで、型チェックを強化しつつ、柔軟なコード設計が可能になります。特に大規模なアプリケーションや複雑なシステムを開発する際に、これらの機能を活用して、型安全なコードを効率的に実装しましょう。