【TypeScript入門】第4回:柔軟な型表現~TypeScriptでより高度なデータ構造を扱う

目次

柔軟な型表現をマスターする:ユニオン型、交差型、リテラル型

これまでの学習で、TypeScriptにおける基本的な型(string, number, boolean, Array, objectなど)や、独自のオブジェクト型を定義するinterfacetypeについて理解を深めてきました。しかし、現実世界のデータは常にシンプルとは限りません。一つの値が複数の型の可能性を持っていたり、複数の型が持つ特性を兼ね備えた新しい型を定義したい場面が多々あります。

第4回では、このような「柔軟な型表現」を可能にするTypeScriptの強力な機能であるユニオン型(Union Types)交差型(Intersection Types)、そしてリテラル型(Literal Types)について深く掘り下げていきます。これらの型を使いこなすことで、より表現力豊かで、かつ堅牢なコードを書くための土台を築きましょう。

デザイナー

柔軟な型表現って、具体的にどんな場面で役立つんでしょうか?これまでの型だけでも十分複雑なのに…

エンジニア

まさにそこがポイントですね。現実世界のデータは、例えば「ユーザーIDは数値か文字列のどちらか」のように、複数の可能性を持つことがよくあります。そういった曖昧さを型システムで明確に表現できると、開発中のバグをぐっと減らせるんですよ。

1. ユニオン型(Union Types):いずれかの型を受け入れる

ユニオン型は、複数の型のうちのいずれか一つであることを示す型です。例えば、「数値型または文字列型」というような状況を表現したい場合に非常に役立ちます。

ユニオン型は、パイプ記号 | を使って複数の型を連結して定義します。

// 数値型か文字列型のいずれかを受け入れる型
type Id = number | string;

function printId(id: Id) {
  console.log(`IDは ${id} です。`);
}

printId(101); // 数値型を渡す
printId("abc-123"); // 文字列型を渡す

// printId(true); // エラー: 型 'boolean' の引数を型 'Id' のパラメーターに割り当てることはできません。

上の例では、Id 型は number 型か string 型のどちらかの値を保持できることを示しています。printId 関数は number または string の引数を受け入れるため、両方の型の値を安全に渡すことができます。

ユニオン型を扱う際、TypeScriptは代入された値が実際にどの型であるかを推論します。

function processValue(value: number | string) {
  if (typeof value === 'string') {
    // このブロック内では、valueはstring型として扱われる
    console.log(value.toUpperCase()); // string型のメソッドが使える
  } else {
    // このブロック内では、valueはnumber型として扱われる
    console.log(value.toFixed(2)); // number型のメソッドが使える
  }
}

processValue(123.456); // "123.46" と出力
processValue("hello typescript"); // "HELLO TYPESCRIPT" と出力

このように、typeof などの「型ガード」を用いることで、ユニオン型で定義された値の実際の型を判断し、それぞれの型に応じた操作を行うことができます。型ガードについては、後の回でさらに詳しく解説します。

エンジニア

ユニオン型で重要なのは、値がどの型であるかを実行時に判断するための「型ガード」ですね。TypeScriptは賢いので、if (typeof value === 'string') と書けば、そのブロック内ではvaluestring型だと推論してくれるんです。

デザイナー

なるほど!型定義は柔軟にしつつも、実際に処理する時は型を特定して安全に扱えるようにする、ということですね。

2. 交差型(Intersection Types):複数の型を組み合わせる

交差型は、複数の型のすべてのプロパティを兼ね備えた新しい型を作成するために使用されます。ユニオン型が「AまたはB」であるのに対し、交差型は「AかつB」であると言えます。

交差型は、アンパサンド & を使って複数の型を連結して定義します。

// Personインターフェース
interface Person {
  name: string;
  age: number;
}

// Contactインターフェース
interface Contact {
  email: string;
  phone: string;
}

// PersonとContactのプロパティを両方持つEmployee型
type Employee = Person & Contact;

const employee: Employee = {
  name: "山田 太郎",
  age: 30,
  email: "taro.yamada@example.com",
  phone: "090-1234-5678"
};

console.log(employee.name);  // Personのプロパティ
console.log(employee.email); // Contactのプロパティ

// const invalidEmployee: Employee = { // エラー: emailプロパティがありません
//   name: "田中 次郎",
//   age: 25,
//   phone: "080-9876-5432"
// };

この例では、Employee 型は Person 型の nameage、そして Contact 型の emailphone全てのプロパティを持っている必要があります。これにより、既存の複数の型を組み合わせて、より複雑で具体的な型を効率的に定義することができます。

オブジェクト型だけでなく、プリミティブ型やリテラル型を交差させることもできますが、その場合は型として存在し得ない型(string & number のような)は never 型となり、その型を持つ値は作成できなくなります。

デザイナー

ユニオン型が「どちらか」だったのに対して、交差型は「どちらも」なんですね。複数のインターフェースを一つにまとめる時に便利そうです。

エンジニア

その通りです。既存の型を組み合わせて、より詳細な情報を持つ新しい型を定義できるので、型定義の重複を避けつつ、強力な型チェックを維持できます。

3. リテラル型(Literal Types):特定の値そのものを型とする

リテラル型は、特定のリテラル値(具体的な値)そのものを型として定義するものです。例えば、"GET"という文字列リテラルや、100という数値リテラルを型として指定できます。

リテラル型は、ユニオン型と組み合わせることで、特定の限られた値のみを受け入れる非常に厳密な型を定義するのに役立ちます。

// HTTPメソッドの種類をリテラル型で定義
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function makeRequest(url: string, method: HttpMethod) {
  console.log(`URL: ${url}, Method: ${method} でリクエストを作成します。`);
}

makeRequest("/api/users", "GET");
makeRequest("/api/products/1", "DELETE");

// makeRequest("/api/items", "PATCH"); // エラー: 型 '"PATCH"' の引数を型 'HttpMethod' のパラメーターに割り当てることはできません。

この例では、HttpMethod 型は "GET", "POST", "PUT", "DELETE" のいずれかの文字列しか受け入れません。これにより、関数が予期しない文字列を受け取ることを防ぎ、コードの安全性を高めることができます。

数値リテラル型や真偽値リテラル型も同様に定義できます。

// 0か1のみを受け入れる型
type Binary = 0 | 1;
let status: Binary = 1;
// status = 2; // エラー: 型 '2' の引数を型 'Binary' のパラメーターに割り当てることはできません。

// 特定の真偽値を型とする(稀だが、意図を明確にしたい場合に有効)
type TrueOnly = true;
let isTrue: TrueOnly = true;
// let isFalse: TrueOnly = false; // エラー

リテラル型は、アプリケーションの状態や、特定のAPI応答のステータスなど、取りうる値が限定されている場合に非常に有効です。

エンジニア

リテラル型は、特にAPIのリクエストメソッドやアプリケーションの状態など、取りうる値が限定されている場合に威力を発揮します。定義された以外の値を受け付けないので、タイプミスや意図しない値によるエラーを防げるんです。

デザイナー

確かに、HTTPメソッドを自分で文字列で入力する時にスペルミスしがちなので、型で制限されていれば安心ですね!

まとめ

デザイナー

今回はユニオン型、交差型、リテラル型と、TypeScriptの柔軟な型表現について深く学べました!特に、具体的な値そのものを型にできるリテラル型は面白いですね。

エンジニア

そうですね。これらの型を使いこなせば、より堅牢で表現力豊かなコードが書けるようになります。次回は、さらにオブジェクト型の制御について掘り下げていきましょう。

第4回では、TypeScriptにおける「柔軟な型表現」の鍵となるユニオン型、交差型、リテラル型の3つを学びました。

  • ユニオン型 (|): 複数の型のいずれか一つを受け入れる場合に利用し、柔軟な値の表現を可能にします。型ガードと組み合わせることで、それぞれの型に応じた安全な処理ができます。
  • 交差型 (&): 複数の型の全てのプロパティを兼ね備えた新しい型を作成する場合に利用し、既存の型を組み合わせて再利用性を高めます。
  • リテラル型: 特定の具体的な値そのものを型として定義し、ユニオン型と組み合わせることで、取りうる値を厳密に制限し、堅牢なコードを構築できます。

これらの型を使いこなすことで、あなたのTypeScriptコードはより表現豊かで、より型安全なものになるでしょう。データ構造やAPIの仕様を正確に型で表現できるようになることで、開発中のエラーを早期に発見し、より安定したアプリケーション開発に繋がります。

次回は、オブジェクトのプロパティを任意にしたり、読み取り専用にしたりと、より詳細なオブジェクト型の制御について学んでいきます。

コメントを残す

入力エリアすべてが必須項目です。メールアドレスが公開されることはありません。

内容をご確認の上、送信してください。