読み込み中...

【TypeScript】ユニオン型の完全ガイド!初心者でもわかる10のコード例

TypeScriptのユニオン型のイラストとともに、初心者向けのわかりやすいガイドと10のコード例を表すテキスト TypeScript
この記事は約30分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

TypeScriptはJavaScriptのスーパーセットとして、静的型チェックの機能を備えたプログラミング言語です。

このため、TypeScriptでは型に関連する多くの便利な機能が利用できます。その中で、「ユニオン型」は非常に重要な位置を占めています。

ユニオン型を使用することで、変数や関数の引数に複数の型を設定することが可能になり、コードの柔軟性を高めることができます。

しかしその一方で、正しく使わないとコードの複雑さを増やす要因ともなり得ます。

本ガイドでは、ユニオン型の基本から高度な使い方、注意点やカスタマイズ方法まで、初心者から上級者までがTypeScriptのユニオン型を効果的に活用するための情報を詳細に解説します。

このガイドを読み進めることで、TypeScriptのユニオン型を使った開発がよりスムーズに、かつ効率的に行えるようになることを期待しています。

それでは、ユニオン型の魅力を一緒に学んでいきましょう。

●TypeScriptとユニオン型の基本

近年、TypeScriptはフロントエンド開発の世界で非常に人気が高まっています。

その理由の一つとして、TypeScriptが提供する型システムが挙げられます。

この型システムを最大限に活用することで、より堅牢なコードを書くことが可能になります。

その中でも、特にユニオン型はその柔軟性と強力さで多くの開発者から注目を受けています。

○TypeScriptとは?

TypeScriptは、JavaScriptに静的型を追加したスーパーセットとして、2012年にMicrosoftによって発表されました。

TypeScriptの最大の特徴は、型アノテーションと高度な型推論を利用して、開発者にエラーを事前に通知することです。

このような静的型チェックにより、バグの予防やリファクタリングの効率化、そしてより明瞭なコードの記述が可能になります。

○ユニオン型とは?

ユニオン型は、複数の型のうちの「いずれかの型」として変数を定義できるTypeScriptの強力な機能です。

具体的には、| を使用して複数の型を組み合わせることができます。

例を見てみましょう。

このコードでは数字か文字列を使って変数を初期化するコードを表しています。

この例では数字と文字列をユニオン型として使用しています。

let value: number | string;
value = 1; // OK
value = "hello"; // OK

このように、value変数はnumber型またはstring型のどちらかの値を持つことができます。

このユニオン型の特徴は、TypeScriptのコンパイラが変数の使用方法を厳格にチェックすることです。

例えば、上のvalue変数に、number型やstring型以外の値を代入しようとすると、コンパイラはエラーを発生させます。

●ユニオン型の使い方

TypeScriptのコーディングにおいて、ユニオン型は非常に有用なツールとなります。

それでは、このセクションではユニオン型の基本的な使用方法を詳しく解説していきます。

特に初心者の方にもわかりやすく、サンプルコードを交えて説明を進めていきます。

○サンプルコード1:基本的なユニオン型の使用

このコードでは、ユニオン型を使って複数の型のどれか一つを取る変数を定義する方法を表しています。

この例では、文字列型もしくは数値型を持つことができる変数を定義し、それぞれの型に適した値を代入しています。

// ユニオン型でstring型またはnumber型を取る変数を定義
let value: string | number;

value = "Hello, TypeScript!"; // string型の値を代入
console.log(value); // Hello, TypeScript!

value = 42; // number型の値を代入
console.log(value); // 42

このサンプルコードを実行すると、まず”Hello, TypeScript!”という文字列が出力され、その後に42という数値が出力されます。

このようにユニオン型を使用すると、1つの変数に異なる複数の型の値を代入することができます。

注意点としては、ユニオン型を使う変数に対して、その型に含まれない値を代入しようとするとコンパイルエラーが発生します。

例えば、上記の変数valueにboolean型の値を代入しようとすると、TypeScriptのコンパイラはエラーを返します。

今回のコードでは、文字列と数値の2つの型を組み合わせてユニオン型を定義しましたが、3つ以上の型を組み合わせることも可能です。

例えば、string | number | booleanといった型定義も作成することができます。

○サンプルコード2:関数引数としてのユニオン型

TypeScriptのユニオン型は非常に便利な機能の一つであり、関数の引数としての活用も多いです。

このコードでは、ユニオン型を関数の引数として使って、異なる型の値を同じ関数に渡すことができることを示しています。

// この例では、stringかnumber型を受け取る関数を定義しています。
function printInput(input: string | number) {
    console.log(`入力されたデータ: ${input}`);
}

printInput("TypeScript");
printInput(2023);

この例では、printInputという関数を定義しています。この関数は、string型またはnumber型のいずれかの引数を受け取ることができます。

そのため、printInput関数を2回呼び出して、1回目は文字列を、2回目は数字をそれぞれ引数として渡しています。

このように、ユニオン型を使用すると、複数の異なる型の値を同じ関数に渡すことができるのです。

このコードを実行すると、次のような出力が得られます。

入力されたデータ: TypeScript
入力されたデータ: 2023

このように、ユニオン型は関数の引数として非常に柔軟な型定義を持たせることができます。

また、ユニオン型を関数の引数として使用する際には、引数の型が具体的にどの型であるかを判定する必要があります。

例えば、文字列専用の処理や数値専用の処理を行いたい場合などです。

引数の型判定を行って、それぞれの型に応じた処理を行うサンプルコードを紹介します。

function handleInput(input: string | number) {
    if (typeof input === "string") {
        console.log(`文字列として処理: ${input.toUpperCase()}`);
    } else {
        console.log(`数値として処理: ${input * 2}`);
    }
}

handleInput("typescript");
handleInput(10);

このコードを実行すると、次のような結果となります。

文字列として処理: TYPESCRIPT
数値として処理: 20

このように、typeofを使用して引数の型を判定し、それぞれの型に応じた処理を行うことができます。

○サンプルコード3:配列内でのユニオン型の活用

TypeScriptでは、配列に格納される要素が複数の異なる型を持つ場面があります。

このような場面でユニオン型を利用することで、効果的に型制約を行うことができます。

このコードでは、数字と文字列の2つの異なる型を持つ要素を格納する配列を作成するコードを表しています。

この例では、配列mixedArrayを宣言して、数字と文字列を混在させて初期化しています。

let mixedArray: (number | string)[] = [1, "Apple", 2, "Banana", 3, "Cherry"];

上記のサンプルコードにおいて、mixedArray(number | string)[]という型を持っています。

この型は「数字または文字列のどちらかの型を持つ要素の配列」という意味になります。

この配列を操作する際にもユニオン型の特性を活かすことができます。

例えば、配列内の要素を取り出して、その型に基づいて処理を行う場面を考えます。

for (let item of mixedArray) {
    if (typeof item === "number") {
        console.log(`${item}は数字です。`);
    } else {
        console.log(`${item}は文字列です。`);
    }
}

この例では、配列mixedArrayの各要素を一つずつ取り出し、その要素の型が数字か文字列かに基づいて異なるメッセージをコンソールに出力しています。

このコードを実際に実行すると、次のような結果となります。

1は数字です。
Appleは文字列です。
2は数字です。
Bananaは文字列です。
3は数字です。
Cherryは文字列です。

配列内でユニオン型を活用することにより、異なる型の要素を簡潔に管理できるだけでなく、その要素に対しての処理も柔軟に行うことができるのがポイントです。

ただ、配列内でユニオン型を使用する際、その要素がどの型に属しているかを明示的に確認しないと、意図しないエラーが発生する可能性があります。

例えば、文字列のメソッドを数字の要素に適用しようとすると、コンパイルエラーとなります。

次に、配列内でユニオン型をさらに活用する方法として、配列の各要素に異なる型のオブジェクトを格納するという応用例を考えます。

type Animal = { type: 'Dog', name: string } | { type: 'Cat', name: string };

let animals: Animal[] = [{ type: 'Dog', name: 'Buddy' }, { type: 'Cat', name: 'Whiskers' }];

for (let animal of animals) {
    if (animal.type === 'Dog') {
        console.log(`${animal.name}は犬です。`);
    } else {
        console.log(`${animal.name}は猫です。`);
    }
}

上記のコードでは、Dog型とCat型という2つの異なる型をユニオン型で組み合わせて、その型の要素を持つ配列animalsを作成しています。

その後、配列の要素を取り出し、その要素の型に基づいて異なるメッセージを出力しています。

このように、ユニオン型を用いて複雑な型の配列も効果的に扱うことができます。

○サンプルコード4:型ガードとユニオン型

TypeScriptでは、複数の型を持つことができるユニオン型と、その型に応じて処理を分岐させる型ガードという機能があります。

これらを効果的に組み合わせることで、安全に異なる型のデータを操作することが可能となります。

このコードでは、型ガードを使ってユニオン型の変数を判別し、それに応じて異なる処理を実行するコードを表しています。

この例では、文字列型と数値型を持つユニオン型を定義し、それを受け取る関数内で型ガードを用いて処理を分岐しています。

type StringOrNumber = string | number;

function processValue(value: StringOrNumber) {
    if (typeof value === "string") {
        // このブロックではvalueはstring型として扱われる
        console.log(value.toUpperCase());
    } else {
        // このブロックではvalueはnumber型として扱われる
        console.log(value * 2);
    }
}

processValue("TypeScript");  // 出力: TYPESCRIPT
processValue(5);             // 出力: 10

このサンプルコードを簡単に解説します。

まず、StringOrNumberというユニオン型を定義しています。

これは、文字列型(string)または数値型(number)のどちらかの型を持つことができる型です。

次に、processValueという関数を定義しています。

この関数はStringOrNumber型のvalueを引数として受け取ります。

関数内では、typeofを使用して型ガードを実装しています。

valueが文字列型の場合、value.toUpperCase()で文字列を大文字にして出力します。

数値型の場合、その値を2倍して出力します。

最後に、この関数に文字列と数値をそれぞれ渡して、結果を確認しています。

実行すると、”TypeScript”は大文字の”TYPESCRIPT”として出力され、5は2倍の10として出力されます。

このように、型ガードを用いることで、ユニオン型の変数が持っている実際の型に基づいて、安全に型に応じた処理を行うことができます。

注意点としては、typeofを使う場合、認識できる型は限られています。

具体的には、”string”, “number”, “boolean”, “object”, “function”などの基本的な型のみです。

もし、より複雑な型の判定を行いたい場合は、ユーザー定義の型ガードを利用する必要があります。

続いて、ユーザー定義の型ガードの例を紹介します。

interface Bird {
    fly(): void;
    layEggs(): void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

function isFish(pet: Bird | Fish): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

このコードでは、BirdFishという二つのインターフェイスを定義しています。

そして、isFishという関数を使って、引数petFish型かどうかを判定するユーザー定義の型ガードを実装しています。

●ユニオン型の応用例

TypeScriptのユニオン型は、非常に柔軟性があり、さまざまな状況や要件に応じて使うことができます。

初心者から上級者まで、わかりやすいサンプルコードを交えて、ユニオン型の高度な応用方法について解説します。

○サンプルコード5:アドバンスドな型制約

ユニオン型は、異なる型の値を一つの変数や関数で取り扱うことができるため、型制約をより詳細に指定する際に非常に役立ちます。

下記のコードでは、数値と文字列のユニオン型を使って、関数で受け取る引数の型制約を設定しています。

この例では、引数として数値または文字列を受け取り、それをそのまま返す関数を定義しています。

// 数値と文字列のユニオン型を持つ関数
function advancedType(value: number | string): number | string {
    return value;
}

let result1 = advancedType(123);  // 数値を受け取る
let result2 = advancedType("Hello");  // 文字列を受け取る

上記のコードでは、関数advancedTypeは、数値または文字列のどちらかの型を持つ引数valueを受け取り、そのまま返す動作をしています。

そのため、result1には数値の123が、result2には文字列の"Hello"がそれぞれ代入されます。

ユニオン型の利点は、複数の型を一つの変数や関数で柔軟に扱えることです。

例えば、あるAPIから返されるデータが数値型である場合と文字列型である場合が考えられる場合、ユニオン型を使用することで、APIからのレスポンスを適切に型制約することができます。

また、このような場面では、型ガードを使用して、関数内で受け取った値の型に応じた処理を分岐させることもできます。

例えば、受け取った値が数値の場合と文字列の場合で異なる処理を行う場面などが考えられます。

上記のコードをさらに発展させ、数値の場合は2倍、文字列の場合は文字列の後ろに" is string"を追加する処理を行う関数を考えてみましょう。

function doubleOrAppend(value: number | string): number | string {
    // 型ガードを使って、引数の型を確認
    if (typeof value === 'number') {
        return value * 2;  // 数値の場合は2倍
    } else {
        return value + " is string";  // 文字列の場合は後ろに追記
    }
}

let result3 = doubleOrAppend(123);       // 246
let result4 = doubleOrAppend("Hello");   // "Hello is string"

関数doubleOrAppendは、型ガードを使用して引数valueの型を確認し、数値の場合は2倍の値を、文字列の場合は後ろに" is string"を追加した値を返しています。

そのため、result3には246が、result4には"Hello is string"がそれぞれ代入されます。

○サンプルコード6:ユニオン型と交差型の併用

TypeScriptは非常に柔軟な型システムを持っており、それによって開発者は安全かつ効率的にコードを書くことができます。その中でユニオン型と交差型は特に有用です。

ここでは、ユニオン型と交差型を一緒に使用する方法について詳しく解説します。

このコードでは、ユニオン型と交差型を組み合わせて、オブジェクトの型を定義する方法を表しています。

この例では、複数の型を同時に持つオブジェクトを作成しています。

// ユニオン型の定義
type Animal = {
    name: string;
    type: 'mammal' | 'bird';
};

// 交差型の定義
type Flying = {
    canFly: boolean;
};

// ユニオン型と交差型の併用
type FlyingAnimal = Animal & Flying;

const eagle: FlyingAnimal = {
    name: 'Eagle',
    type: 'bird',
    canFly: true
};

このサンプルコードでは、まずAnimalというユニオン型を定義しています。

この型はnametypeという2つのプロパティを持っています。次に、Flyingという交差型を定義しています。

この型は飛べる動物を表すためのcanFlyという真偽値のプロパティを持っています。

最後に、FlyingAnimalという型を定義するときにユニオン型のAnimalと交差型のFlyingを組み合わせています。

この結果、FlyingAnimalname, type, そしてcanFlyという3つのプロパティを持つことになります。

このようにユニオン型と交差型を組み合わせることで、複数の型の特性を持つ新しい型を作成することができます。

このサンプルコードを実行すると、eagleという変数にFlyingAnimal型のオブジェクトが代入されます。

このオブジェクトはnameに”Eagle”、typeに”bird”、canFlytrueという値を持っています。

さらに、この型定義は以下のようなオブジェクトを禁止する強力な型制約を持っています。

例えば、次のようなオブジェクトはFlyingAnimal型としては許容されません。

const penguin: FlyingAnimal = {
    name: 'Penguin',
    type: 'bird',
    canFly: false // Penguinは飛べないが、ここでのcanFlyは必要
};

このコードはエラーとなります。というのも、typeプロパティに”bird”が指定されているため、canFlyプロパティも必要となるからです。

このように、ユニオン型と交差型をうまく組み合わせることで、複雑なビジネスロジックや要件をコード上で表現することが可能となります。

また、この組み合わせを使うことで、柔軟でありながらも堅牢な型の定義を行うことができます。

○サンプルコード7:ユニオン型を使った型マッピング

このコードでは、TypeScriptのユニオン型と、それを使った型マッピングの仕組みを使って、より複雑な型の変換や操作を表しています。

この例では、既存の型のプロパティを読み取り、それに基づいて新しい型を生成しています。

// 既存の型定義
type Animal = {
    kind: 'dog' | 'cat',
    name: string
};

// 型マッピングを利用した新しい型の生成
type AnimalNameMap = {
    [K in Animal['kind']]: string
};

// 上記の型マッピングを使用した変数の例
const animalNames: AnimalNameMap = {
    dog: 'ハチ',
    cat: 'タマ'
};

まず、Animalという型を定義しました。

これは動物の種類(kind)と名前(name)のプロパティを持つシンプルなオブジェクト型です。

ここでは、動物の種類として犬と猫をユニオン型で指定しています。

次に、型マッピングの特性を活用してAnimalNameMapという新しい型を生成しています。

この型は、Animalkindプロパティの各値(つまりdogcat)をキーとして持ち、それぞれの値に関連する名前(文字列型)を値として持つオブジェクト型です。

最後に、この新しい型AnimalNameMapを使ってanimalNamesという変数を定義しています。

この変数は、犬と猫の名前をマッピングするオブジェクトとなります。

このサンプルコードを実際にTypeScriptで実行すると、animalNames変数は正しく型チェックされ、犬や猫の名前をマッピングするオブジェクトとして利用できます。

このように、TypeScriptのユニオン型と型マッピングを組み合わせることで、動的なキーを持つオブジェクトの型を効果的に定義できます。

○サンプルコード8:タグ付きユニオンを利用したデザインパターン

TypeScriptの強力な型機能の中で、特に注目すべきは「タグ付きユニオン」というパターンです。

このデザインパターンは、異なる型を持つオブジェクトを一つの共通のタグでまとめることができ、それによってコンパイル時に型の安全性を確保することが可能になります。

特に、複雑な構造を持つデータや、異なる種類のオブジェクトを取り扱う際に非常に有効です。

このコードではタグ付きユニオンを使って、動物の情報を表現するコードを表しています。

この例では、犬と鳥の2種類の動物をタグ付きユニオンで表現しています。

// タグ付きユニオンの型定義
type Animal = 
  { type: 'dog', name: string, bark: string } |
  { type: 'bird', sound: string, canFly: boolean };

// 犬のオブジェクト
const dog: Animal = {
  type: 'dog',
  name: 'Taro',
  bark: 'わんわん'
};

// 鳥のオブジェクト
const bird: Animal = {
  type: 'bird',
  sound: 'ピー',
  canFly: true
};

// 動物の情報を表示する関数
function showAnimalInfo(animal: Animal) {
  switch (animal.type) {
    case 'dog':
      console.log(`名前: ${animal.name}、鳴き声: ${animal.bark}`);
      break;
    case 'bird':
      console.log(`鳴き声: ${animal.sound}、飛ぶことができる: ${animal.canFly}`);
      break;
  }
}

showAnimalInfo(dog);
showAnimalInfo(bird);

このコードではまず、タグ付きユニオンAnimalを定義しています。

このユニオンにはdogbirdという2つのタグが含まれています。

そして、各動物のオブジェクトを作成し、その後それらの情報を表示する関数showAnimalInfoを定義しています。

関数showAnimalInfo内では、引数として渡される動物のtypeプロパティを使って、どの動物の情報を表示するのかを判断しています。

これにより、犬の場合は名前と鳴き声、鳥の場合は鳴き声と飛ぶことができるかどうかを表示しています。

この例を実際に実行すると、次のような結果が得られます。

犬の情報がまず表示され、その後に鳥の情報が表示されます。

名前: Taro、鳴き声: わんわん
鳴き声: ピー、飛ぶことができる: true

このようなタグ付きユニオンを使うことで、異なる型のデータを効率的に一つの型でまとめることができます。

また、コンパイル時に型の整合性をチェックすることができるので、ランタイムエラーを大幅に削減することができます。

応用例として、動物の種類をさらに増やしたり、共通のプロパティを持つ別のオブジェクトを作成することが考えられます。

例えば、次のように猫の情報も追加することができます。

type Animal = 
  { type: 'dog', name: string, bark: string } |
  { type: 'bird', sound: string, canFly: boolean } |
  { type: 'cat', name: string, meow: string };

このように、タグ付きユニオンを活用することで、異なる型のデータを効果的に扱うことができます。

初心者から上級者まで、このデザインパターンを知っておくとTypeScriptのコーディングがさらに楽しく、効果的になるでしょう。

○サンプルコード9:条件型とユニオン型の組み合わせ

TypeScriptの型システムは非常に強力で、多くの高度な型操作をサポートしています。

その中でも、条件型とユニオン型の組み合わせは特に興味深いテクニックの一つです。

条件型を使うと、ある型が特定の条件を満たすかどうかに基づいて、別の型を返すことができます。

ユニオン型と組み合わせることで、さまざまな型の組み合わせを柔軟に扱うことができます。

このコードでは、条件型とユニオン型を組み合わせて、与えられた型が文字列型か数値型かを判定する型を表しています。

この例では、TypeIsという条件型を定義して、文字列型ならば"string"、数値型ならば"number"、それ以外の型ならば"other"を返すようにしています。

type TypeIs<T> = T extends string ? "string" :
                T extends number ? "number" : "other";

// 使用例
type Result1 = TypeIs<string>;  // "string"
type Result2 = TypeIs<number>;  // "number"
type Result3 = TypeIs<boolean>; // "other"

このコードは、TypeIs型に指定された型が文字列型か数値型かを判定しています。

extendsキーワードを使用して、Tが特定の型に該当するかどうかを判定しています。

Tが文字列型ならば、結果として"string"を返し、数値型ならば"number"を返します。

それ以外の型は"other"として扱われます。

実際にTypeIsを使用すると、上記のコメントに表すように、それぞれの型に合わせた結果が得られます。

このような型の定義は、関数やクラス、インターフェースの定義の中で、特定の型の振る舞いを制約したい場合に役立ちます。

たとえば、APIから取得したデータの型に応じて、異なる処理を行いたい場合などに有効です。

しかし、この手法を使用する際の注意点として、条件型は複雑になると読みにくくなる可能性があります。

そのため、適切なコメントやドキュメントを残して、他の開発者が理解しやすいようにすることが大切です。

応用例として、ユニオン型の各要素が特定の型に該当するかどうかを判定するAllStrings型を紹介します。

この型は、ユニオン型のすべての要素が文字列型である場合にtrueを、それ以外の場合にfalseを返します。

type AllStrings<T> = T extends (infer U)[] ? (U extends string ? true : false) : false;

// 使用例
type Array1 = AllStrings<[string, string, string]>;  // true
type Array2 = AllStrings<[string, number, string]>;  // false

このコードでは、AllStrings型に与えられたユニオン型のすべての要素が文字列型であるかを判定しています。

もし一つでも文字列型でない要素が含まれている場合、結果としてfalseが返されます。

○サンプルコード10:ユニオン型を用いたエラーハンドリング

TypeScriptはJavaScriptのスーパーセットとして生まれ、静的な型付けを提供することで、大規模なアプリケーションの開発やメンテナンスを助けています。

この中でも「ユニオン型」は、TypeScriptの強力な型の1つとして知られています。

このコードではユニオン型を使ってエラーハンドリングを行う方法を表しています。

この例では関数の戻り値として、成功した場合のデータまたはエラー情報を返す形で利用しています。

type Success<T> = {
    isSuccess: true;
    data: T;
};

type Failure = {
    isSuccess: false;
    error: string;
};

type Result<T> = Success<T> | Failure;

function fetchData(): Result<string> {
    try {
        // ここにデータを取得する処理を記述
        let data = "データの取得に成功!";
        return {
            isSuccess: true,
            data: data
        };
    } catch (e) {
        return {
            isSuccess: false,
            error: "データの取得に失敗しました"
        };
    }
}

const result = fetchData();
if (result.isSuccess) {
    console.log(result.data); // データの取得に成功!
} else {
    console.log(result.error); // データの取得に失敗しました
}

この例では、Success型とFailure型をユニオン型で組み合わせることでResult型を作成しています。

fetchData関数はResult<string>型の戻り値を返すため、呼び出し元で成功した場合のデータ処理やエラー処理を分岐させることができます。

このようにユニオン型を利用することで、関数の戻り値に対するエラーハンドリングを効果的に行うことができます。

このコードを実行すると、データの取得が成功した場合はデータの取得に成功!と出力され、失敗した場合はデータの取得に失敗しましたと出力されます。

●ユニオン型の注意点と対処法

TypeScriptでのプログラミングを行っていると、非常に強力なユニオン型を頻繁に使用することがあるでしょう。

しかし、その使い方には注意が必要です。

ここでは、ユニオン型の使い方におけるよくある罠とその回避策、さらに型安全を確保するためのベストプラクティスを解説します。

○ユニオン型の罠と回避策

□誤った型の代入

ユニオン型を使用すると、いくつかの型の中から1つを選んで使用することができます。

しかし、間違った型を代入してしまうことが考えられます。

このコードではstring | number型のユニオン型を使用しています。

この例ではvalueに文字列と数値のどちらも代入することができます。

   let value: string | number;
   value = "Hello"; // 正しい代入
   value = 123; // 正しい代入

しかし、次のような代入はTypeScriptの型チェックに引っかかります。

   value = true; // エラー! boolean型は許容されていない

このようなエラーを避けるためには、変数の型を明確に宣言することが大切です。

□型ガードの不足

ユニオン型を扱う際、特定の型のみを対象とした処理を行いたい場合があります。

その際、適切な型ガードを設定しないと予期しないエラーが発生する可能性があります。

例えば、次のコードでは、processValue関数はユニオン型を受け取り、数値の場合のみ処理を行います。

   function processValue(value: string | number) {
       if (typeof value === 'number') {
           console.log(value * 2); // 数値の場合のみ2倍して出力
       }
   }

この例ではtypeofを使って型ガードを行っています。

このような型ガードがないと、文字列に対して数値の操作を行ってしまうリスクがあります。

○型安全を確保するためのベストプラクティス

ユニオン型を安全に使用するためのベストプラクティスをいくつか紹介します。

□狭い範囲の型を使用する

必要な型だけをユニオン型に含めることで、誤った型の使用を防ぐことができます。

不要な型を含めてしまうと、意図しない代入が可能になってしまう可能性があります。

□明示的な型アノテーションを使用する

変数や関数の戻り値に対して、型アノテーションを明示的に付けることで、間違った型の使用を早期に発見できます。

□型ガードを活用する

typeofinstanceofなどの型ガードを適切に使用することで、コードの中で特定の型に対する操作を安全に行うことができます。

このように、ユニオン型は非常に強力なツールですが、その使用には注意が必要です。

上述の罠やベストプラクティスを意識して、TypeScriptのユニオン型を効果的に活用しましょう。

●ユニオン型のカスタマイズ方法

ユニオン型はTypeScriptで非常に有用な型ですが、場合によってはカスタマイズが求められることがあります。

ここでは、ユニオン型をカスタマイズする方法を2つのサンプルコードとともに詳しく紹介します。

○カスタムユニオン型の作成

TypeScriptでのユニオン型は、複数の型を「|」で繋げて表現されます。

しかし、特定のルールに従ってユニオン型を組み合わせることで、さらに柔軟なカスタムユニオン型を作成することができます。

このコードでは、数字と文字列のユニオン型をカスタマイズして特定の文字列のみを受け取るカスタムユニオン型を作成するコードを表しています。

この例では、数字か、”small”、”medium”、”large”の3つの文字列のいずれかを受け取るカスタムユニオン型を定義しています。

type CustomUnion = number | "small" | "medium" | "large";

// この変数は有効です。
let size1: CustomUnion = "small";

// この変数はエラーとなります。
let size2: CustomUnion = "extra-large"; 

上記のコードを実際に実行すると、size2の定義でTypeScriptの型エラーが発生します。

これは、”extra-large”という文字列がCustomUnion型に定義されていないためです。

○ユーティリティ型を活用した高度なユニオン型の作成

TypeScriptには、ユニオン型をより高度にカスタマイズするためのユーティリティ型が備わっています。

これを利用することで、複雑な条件を持つカスタムユニオン型を効率よく定義することができます。

このコードでは、Partialというユーティリティ型を使って、オブジェクトのプロパティを部分的なユニオン型として扱うコードを表しています。

この例では、User型のプロパティを部分的に持つことができる新しい型を作成しています。

type User = {
  id: number;
  name: string;
  age: number;
};

type PartialUser = Partial<User>;

const user1: PartialUser = {
  id: 1,
  name: "Taro"
};

const user2: PartialUser = {
  age: 25
};

上記のコードにおいて、PartialUser型はUser型の全てのプロパティをオプショナルとして扱います。

そのため、user1user2のように部分的なプロパティを持つオブジェクトを作成することができます。

まとめ

TypeScriptは、JavaScriptのスーパーセットとして、静的型システムを持つことで知られています。

その中で、ユニオン型は非常に強力なツールとして、多くの開発者たちに利用されています。

この記事では、ユニオン型の基本から応用、注意点、カスタマイズ方法まで、初心者から上級者までの読者が理解しやすいように詳細に解説しました。

このガイドを通じて、読者の皆様がTypeScriptのユニオン型に対する理解を深め、日々の開発に役立てることを心から願っています。

今後もTypeScriptの機能やユーティリティ、ベストプラクティスなど、さまざまな情報を提供していきますので、是非ともご期待ください。