アルアカ - Arcadia Academia

Arcadia Academiaは「エンジニアリングを楽しむ」を合言葉に日本のデジタル競争力を高めることをミッションとするテックコミュニティです。

TypeScriptにおける高度な型システム

Featured image of the post

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型はPersonEmployeeの両方のプロパティを持つことが必要です。交差型は、複数の型を統合して、新しい型を定義したい場合に役立ちます。

3. 型ガード(Type Guards)

ユニオン型を使う場合、実際に使われる値の型を安全に確認する必要があります。型ガードを使用することで、特定の型に応じた処理を実行できます。typeofinstanceofを使って型を判定し、適切な処理を行います。

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

この例では、Tstringに割り当て可能であれば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の高度な型システムを活用することで、より柔軟で堅牢なコードを記述することができます。ユニオン型や交差型、条件付き型、マッピング型などを組み合わせることで、型チェックを強化しつつ、柔軟なコード設計が可能になります。特に大規模なアプリケーションや複雑なシステムを開発する際に、これらの機能を活用して、型安全なコードを効率的に実装しましょう。



▼ 目次