TypeScriptでユニオン型を使った可変長引数の関数定義方法を解説

TypeScriptにおけるユニオン型と可変長引数を活用することで、柔軟で拡張性のある関数を定義することが可能です。ユニオン型を使うことで、異なる型の引数を一つの関数に渡すことができ、可変長引数を併用することで、引数の数を事前に固定することなく、任意の数の引数を処理できます。これにより、より汎用的で強力な関数を作成でき、複雑なロジックを簡潔に表現することが可能です。本記事では、ユニオン型と可変長引数の基本概念から、それらを組み合わせた関数定義の方法、実際の応用例やベストプラクティスまでを詳しく解説していきます。

目次

ユニオン型の基本

ユニオン型とは、TypeScriptで複数の型を一つにまとめることができる型のことです。ユニオン型を使うことで、変数や関数の引数が複数の異なる型を許容することが可能になります。たとえば、string型かnumber型のいずれかを受け取る変数を定義する場合、ユニオン型を使用して次のように記述します。

let value: string | number;

このvalueは、stringもしくはnumberのどちらかの型として扱うことができ、用途に応じて柔軟に型を指定できるため、コードの再利用性や拡張性が高まります。

ユニオン型のメリット

ユニオン型の主なメリットは、以下の通りです。

柔軟性の向上

一つの関数や変数に対して、複数の異なる型を許容できるため、さまざまなケースに対応可能です。これにより、特定のデータ型に縛られず、汎用的な関数やクラスを作成できます。

型安全性を保持

TypeScriptは、ユニオン型を使うことで動的言語のような柔軟性を持ちながら、型安全性を保持します。コンパイル時に型チェックが行われるため、実行時に予期しないエラーが発生するリスクを減らすことができます。

このように、ユニオン型はTypeScriptにおいて、型の柔軟性を保ちながらも型安全を実現するための重要な機能となります。

可変長引数の基礎

可変長引数とは、関数が任意の数の引数を受け取ることができる構文を指します。TypeScriptでは、スプレッド構文 (...) を使用して可変長引数を扱うことができます。この機能により、関数に渡される引数の数が固定されていない場合でも、簡単に処理が可能になります。

たとえば、任意の数の数値を受け取り、その合計を計算する関数は次のように定義できます。

function sum(...numbers: number[]): number {
    return numbers.reduce((total, num) => total + num, 0);
}

このsum関数は、1つでも複数でも数値を引数として受け取ることができ、その数に応じて処理が行われます。

可変長引数の使いどころ

引数の数が不定の場合

関数に渡す引数の数が事前に決まっていない場合や、動的に変わる可能性がある場合に、可変長引数が非常に有効です。例えば、複数の値をまとめて処理したり、動的に生成されたリストを渡す場合などです。

処理を簡潔に記述

複数の引数を1つの配列にまとめる必要がなく、関数内で直接スプレッド構文を使って処理できるため、コードが簡潔になり、可読性が向上します。

このように、可変長引数は、柔軟な関数設計を実現し、複数の引数を効率よく扱うための便利な機能です。次に、ユニオン型と組み合わせることで、より強力な関数を作成する方法を説明します。

ユニオン型と可変長引数の組み合わせ

TypeScriptでは、ユニオン型と可変長引数を組み合わせることで、関数が異なる型の複数の引数を柔軟に受け取ることが可能になります。この組み合わせを活用することで、同一の関数で異なる型のデータを処理し、コードの汎用性を高めることができます。

たとえば、string型とnumber型のどちらかを任意の数だけ引数にとり、それを処理する関数を作成する場合は、次のように定義できます。

function processValues(...values: (string | number)[]): void {
    values.forEach(value => {
        if (typeof value === 'string') {
            console.log(`String: ${value}`);
        } else {
            console.log(`Number: ${value}`);
        }
    });
}

この関数processValuesは、string型やnumber型の値を複数受け取ることができ、それぞれの型に応じた処理を行います。ユニオン型を使用することで、異なる型の引数を混在させることが可能となり、汎用性が大きく向上します。

ユニオン型と可変長引数を使う利点

柔軟なデータ処理

ユニオン型と可変長引数の組み合わせにより、同一の関数が複数のデータ型を処理できるようになるため、コードがシンプルになり、同じような処理を繰り返す必要がなくなります。

型安全な動的な引数処理

ユニオン型を使用してもTypeScriptの型チェック機能が維持され、関数の中で引数の型に応じた処理を安全に行うことができます。このため、ランタイムエラーのリスクを低減し、バグの発生を防ぎやすくなります。

このように、ユニオン型と可変長引数の組み合わせにより、柔軟かつ型安全な関数を作成でき、異なるデータ型を扱うシチュエーションでも、より効率的なコードが書けるようになります。

型安全な関数の作成

ユニオン型と可変長引数を組み合わせることで、柔軟な関数を作成できますが、その際、型安全性を確保することが非常に重要です。TypeScriptの強力な型チェック機能を活用し、関数内で適切に型を判断することで、予期しないエラーを防ぐことが可能です。

たとえば、ユニオン型を使ってstringnumberを引数にとり、それぞれの型に応じた処理を行う型安全な関数を定義してみましょう。

function displayValues(...values: (string | number)[]): void {
    values.forEach(value => {
        if (typeof value === 'string') {
            console.log(`文字列: ${value.toUpperCase()}`);
        } else if (typeof value === 'number') {
            console.log(`数値: ${value.toFixed(2)}`);
        }
    });
}

このdisplayValues関数は、string型の場合は文字列を大文字に変換し、number型の場合は小数点以下2桁まで表示します。このように、各引数の型に応じて適切な処理を行うことで、型の安全性を保ちながら柔軟な処理が可能になります。

型ガードによる安全性の確保

`typeof`での型チェック

上記の例で使用しているtypeofは、ユニオン型の要素ごとに型を識別するための基本的な型ガードです。stringnumberといったプリミティブ型に対して有効で、条件に基づいて型を分岐させ、安全な処理を行います。

if (typeof value === 'string') {
    // string型の処理
}

カスタム型ガード

さらに複雑な型チェックが必要な場合は、カスタム型ガードを定義することも可能です。たとえば、オブジェクト型のユニオン型であれば、instanceofや独自の関数を使って型を判別できます。

function isString(value: any): value is string {
    return typeof value === 'string';
}

TypeScriptの型推論を活用

TypeScriptの型推論機能を使うことで、コードの可読性を高めつつ、型安全な関数を構築できます。引数や戻り値の型を明示的に指定する必要がないケースでも、TypeScriptは自動的に適切な型を推論してくれるため、過剰な型宣言を避け、シンプルなコードを書けます。

例:戻り値の型推論

function combineValues(...values: (string | number)[]): string {
    return values.join(", ");
}

この関数は戻り値としてstring型を返すことが確実であり、明示的に戻り値の型を指定していない場合でもTypeScriptが自動的に推論します。

このように、ユニオン型と可変長引数を組み合わせた型安全な関数を作成することで、柔軟性と信頼性を両立したコードが実現可能です。

関数の型推論と制約

ユニオン型と可変長引数を使った関数では、TypeScriptの型推論が非常に有効に機能しますが、同時に特定の制約が発生することもあります。型推論をうまく活用しつつ、制約を理解しておくことで、より効率的なコードを書くことが可能になります。

TypeScriptの型推論の仕組み

TypeScriptは、変数や関数の引数、戻り値に対して明示的な型指定を行わなくても、文脈やコードの内容から自動的に型を推論します。ユニオン型を使う場合でも、TypeScriptは適切な型を推論し、コンパイル時に型チェックを行ってくれます。

例えば、次の関数では戻り値の型を明示的に指定していませんが、TypeScriptは自動的にstring型であることを推論します。

function combineNames(...names: string[]): string {
    return names.join(", ");
}

この場合、namesが全てstring型であるため、戻り値もstringとして扱われます。TypeScriptが自動で型推論を行うことで、余分な型注釈を省略できます。

ユニオン型の制約

ユニオン型を使った関数は非常に柔軟ですが、いくつかの制約があります。特に、ユニオン型の値に対して直接的な操作を行う場合、すべての型に対して有効なメソッドやプロパティしか使用できないという制約があります。

例えば、次のような関数を考えてみます。

function processItems(...items: (string | number)[]): void {
    items.forEach(item => {
        console.log(item.length); // エラー: 'length'プロパティは'number'に存在しない
    });
}

この場合、string型にはlengthプロパティが存在しますが、number型には存在しないため、TypeScriptはエラーを発生させます。このような場合は、型ガードを使用して型ごとに処理を分ける必要があります。

型ガードによる制約の回避

ユニオン型の制約を回避するためには、型ガードを使ってそれぞれの型に応じた処理を行う必要があります。前述の例では、次のようにtypeofを使って型を確認します。

function processItems(...items: (string | number)[]): void {
    items.forEach(item => {
        if (typeof item === 'string') {
            console.log(item.length); // string型の処理
        } else {
            console.log(item.toFixed(2)); // number型の処理
        }
    });
}

このように、ユニオン型の要素を処理する際には、型ごとに分岐して適切な処理を行うことで、型安全性を維持しつつ柔軟なコードを実現できます。

複雑なユニオン型と型推論の限界

非常に複雑なユニオン型や、ネストしたユニオン型を使用する場合、TypeScriptの型推論が困難になることがあります。特に、ユニオン型とジェネリクスを組み合わせた場合、推論が不明確になり、手動で型注釈を行う必要があるケースも出てきます。

例えば、次のようなジェネリック関数を考えます。

function getValues<T extends string | number>(...values: T[]): T[] {
    return values;
}

この場合、Tの型推論が曖昧になることがあります。こういったケースでは、型注釈を明確に記述することが推奨されます。

このように、TypeScriptの型推論は強力なツールですが、ユニオン型や可変長引数を使用する際には、型の制約や限界を理解して適切にコードを書く必要があります。

実際のユースケース

ユニオン型と可変長引数を組み合わせた関数は、実際の開発現場でさまざまなシチュエーションに応用できます。ここでは、実際のプロジェクトで役立つユースケースをいくつか紹介し、これらの技術がどのように実践で活用されるかを見ていきます。

ユースケース1: 多様な入力を処理するロギング関数

アプリケーション開発では、さまざまな型のデータをロギングする場面がよくあります。例えば、エラーメッセージ、ステータスコード、オブジェクトのデバッグ情報など、さまざまなデータ型を一つの関数で効率よくログに出力することが求められます。ユニオン型と可変長引数を使えば、どの型のデータでも1つのロギング関数で処理できます。

function logMessages(...messages: (string | number | object)[]): void {
    messages.forEach(message => {
        if (typeof message === 'string') {
            console.log(`String: ${message}`);
        } else if (typeof message === 'number') {
            console.log(`Number: ${message}`);
        } else {
            console.log(`Object: ${JSON.stringify(message)}`);
        }
    });
}

// 使用例
logMessages("エラーメッセージ", 404, { status: "Not Found" });

このように、ロギング関数は異なる型のデータを一度に処理でき、アプリケーション全体のロギングを一元化できます。

ユースケース2: フォーマット可能なメッセージ表示

Webアプリケーションやモバイルアプリケーションでは、ユーザーに対してメッセージを表示する際、さまざまな型のデータをフォーマットする必要があります。例えば、ユーザー名(文字列)や残り時間(数値)など、異なるデータ型を一つの関数で処理する場合に、ユニオン型と可変長引数が役立ちます。

function formatMessage(template: string, ...values: (string | number)[]): string {
    return values.reduce((formatted, value) => {
        return formatted.replace("%s", value.toString());
    }, template);
}

// 使用例
const message = formatMessage("こんにちは、%sさん。あと%s分です。", "太郎", 5);
console.log(message); // "こんにちは、太郎さん。あと5分です。"

この関数は、テンプレート文字列と可変長の引数を受け取り、指定された箇所に適切な値を挿入してフォーマットします。異なる型(文字列と数値)を組み合わせて扱えるため、柔軟性が高く、幅広いケースで使えます。

ユースケース3: フィルタリングと集計処理

複数のデータ型を一度に処理する必要がある場合、ユニオン型と可変長引数を活用した関数が非常に便利です。たとえば、文字列や数値の配列から特定の条件に合う要素をフィルタリングし、集計する処理を行う場合です。

function filterAndSum(...values: (string | number)[]): number {
    return values.filter(value => typeof value === 'number')
                 .reduce((sum, num) => sum + (num as number), 0);
}

// 使用例
const total = filterAndSum(10, "skip", 20, 30, "ignore");
console.log(total); // 60

この関数は、与えられた引数から数値だけをフィルタリングして、その合計を算出します。文字列やその他の型は無視されるため、数値データの集計処理に適しています。

ユースケース4: HTTPリクエストのパラメータ処理

HTTPリクエストにおいて、URLパラメータやボディに含まれるデータが異なる型で送られることがあります。ユニオン型を使うことで、さまざまな型のパラメータを受け取って処理する関数を簡単に定義できます。

function processRequestParams(...params: (string | number | boolean)[]): void {
    params.forEach(param => {
        if (typeof param === 'string') {
            console.log(`String param: ${param}`);
        } else if (typeof param === 'number') {
            console.log(`Number param: ${param}`);
        } else {
            console.log(`Boolean param: ${param}`);
        }
    });
}

// 使用例
processRequestParams("userID", 123, true);

この関数は、リクエストで渡されるさまざまな型のパラメータを適切に処理できます。こうした汎用的な関数は、WebアプリケーションやAPIの設計において非常に役立ちます。

これらのユースケースからも分かるように、ユニオン型と可変長引数を組み合わせることで、実際のプロジェクトで多様なデータを扱いながらも、型安全性を保ちつつ柔軟な関数を作成できます。

エラーハンドリング

ユニオン型と可変長引数を組み合わせた関数を使う場合、複数の異なる型を処理するため、エラーハンドリングが特に重要になります。引数の型に応じた処理や、予期しない値が渡されたときの対処方法を適切に設計しておくことで、プログラムの信頼性と安定性を確保できます。

ユニオン型でのエラーチェック

ユニオン型を使用している場合、引数が複数の異なる型のどれかであることが前提です。しかし、実際に関数が呼び出された際、予期しない型が渡されることがあります。こうした場合には、型チェックを行い、適切なエラーメッセージを表示するか、処理を中断する必要があります。

例えば、以下のようにstringnumber型のみを受け取る関数を定義し、それ以外の型が渡された場合にはエラーをスローします。

function validateValues(...values: (string | number)[]): void {
    values.forEach(value => {
        if (typeof value !== 'string' && typeof value !== 'number') {
            throw new Error(`Invalid value type: ${typeof value}`);
        }
    });
}

この関数では、stringnumber以外の型が渡された場合にエラーが発生します。これにより、予期しない入力に対して明確に対処でき、コードの安全性が向上します。

エラーを適切にハンドルする方法

エラーハンドリングの基本は、エラーが発生した場合にプログラムの正常な動作が妨げられないようにすることです。JavaScriptおよびTypeScriptでは、try-catch構文を使用してエラーハンドリングを行うのが一般的です。これにより、例外が発生しても処理が停止することなく、適切にエラーをキャッチして対処できます。

以下は、try-catchを使用してエラーをハンドルする例です。

function processInput(...inputs: (string | number)[]): void {
    try {
        inputs.forEach(input => {
            if (typeof input !== 'string' && typeof input !== 'number') {
                throw new Error(`Invalid input type: ${typeof input}`);
            }
            console.log(`Processing input: ${input}`);
        });
    } catch (error) {
        console.error(`Error occurred: ${(error as Error).message}`);
    }
}

// 使用例
processInput("hello", 42, true);  // "Error occurred: Invalid input type: boolean"

この関数では、エラーが発生した場合でもcatchブロックでエラーメッセージを出力し、処理が止まらないようにしています。

適切なエラーメッセージの設計

エラーメッセージは、発生したエラーの原因を明確に説明するものでなければなりません。特に、ユニオン型を使用する場合、どの型が期待されていたのか、そしてどの型が渡されたのかを明確にする必要があります。

function safeProcess(...values: (string | number)[]): void {
    values.forEach(value => {
        if (typeof value !== 'string' && typeof value !== 'number') {
            console.error(`Error: Expected string or number, but got ${typeof value}`);
        } else {
            console.log(`Processing: ${value}`);
        }
    });
}

// 使用例
safeProcess("text", 123, {}, "more text");

この関数では、予期しない型が渡された場合に具体的なエラーメッセージを表示し、どの型が不適切であるかを明確にします。

例外を利用した堅牢なプログラム設計

ユニオン型や可変長引数を使用する関数では、例外処理を戦略的に活用することで、予期しない入力やエラーに対しても堅牢なプログラムを設計することができます。特に、予測不可能な入力や外部からのデータを処理する際には、適切な例外処理を行うことで、プログラムが予期しないクラッシュを回避し、ユーザーに適切なフィードバックを提供することが可能です。

例外処理を組み込んだ関数は、次のように設計します。

function calculateAverage(...values: (number | string)[]): number {
    let total = 0;
    let count = 0;

    values.forEach(value => {
        if (typeof value === 'number') {
            total += value;
            count++;
        } else if (typeof value === 'string') {
            const parsed = parseFloat(value);
            if (!isNaN(parsed)) {
                total += parsed;
                count++;
            } else {
                throw new Error(`Invalid string for number conversion: ${value}`);
            }
        } else {
            throw new Error(`Unsupported type: ${typeof value}`);
        }
    });

    if (count === 0) {
        throw new Error("No valid numbers provided");
    }

    return total / count;
}

// 使用例
try {
    console.log(calculateAverage(10, "20", "invalid", 30)); 
} catch (error) {
    console.error(`Error: ${(error as Error).message}`);
}

この関数は、numberと数値に変換可能なstringを処理し、それ以外の型や変換できない文字列には適切なエラーメッセージを表示します。

ユニオン型と可変長引数を使用する場合のエラーハンドリングは、型安全性を確保しつつ、予期しない入力やエラーをしっかり処理するために重要です。正しくエラーをハンドルすることで、堅牢で信頼性の高いアプリケーションを開発できます。

テストとデバッグ

ユニオン型と可変長引数を使用した関数は、柔軟性が高い反面、さまざまなケースで適切に動作することを確認する必要があります。ここでは、こうした関数に対して行うテストとデバッグの手法について説明し、バグや予期しない動作を防ぐための方法を紹介します。

ユニオン型と可変長引数関数のテスト戦略

ユニオン型と可変長引数を使用する関数は、複数の異なる型や長さの引数に対応するため、さまざまな組み合わせでテストを行うことが重要です。特に、以下のテストケースを網羅する必要があります。

テストケース1: 型ごとの処理

ユニオン型を使用する場合、それぞれの型に応じた処理が正しく行われることを確認します。例えば、string型とnumber型を受け取る関数であれば、それぞれの型に対して期待通りの処理が行われているかをチェックします。

function processValues(...values: (string | number)[]): string {
    return values.map(value => {
        if (typeof value === 'string') {
            return `String: ${value}`;
        } else {
            return `Number: ${value}`;
        }
    }).join(", ");
}

// テスト
console.assert(processValues("hello", 42) === "String: hello, Number: 42", "Test case 1 failed");

このテストでは、stringnumberが正しく処理され、結果が期待通りの文字列になっているかを確認します。

テストケース2: 可変長引数の処理

可変長引数を使用する関数では、引数の数が異なる場合でも正しく処理されるかどうかをテストします。引数が1つ、複数、あるいは空の場合など、さまざまな状況をシミュレートします。

console.assert(processValues("a") === "String: a", "Test case 2 failed");
console.assert(processValues() === "", "Test case 3 failed");

これにより、可変長引数の処理が適切に行われていることを確認できます。

テストケース3: 不正なデータの処理

ユニオン型では、指定されていない型が引数に含まれた場合にエラーが発生するかどうかも重要なテストです。TypeScriptでは型安全性が担保されていますが、ランタイムでのテストやユーザー入力の検証も必要です。

function safeProcessValues(...values: (string | number)[]): string {
    return values.map(value => {
        if (typeof value !== 'string' && typeof value !== 'number') {
            throw new Error(`Invalid type: ${typeof value}`);
        }
        return value.toString();
    }).join(", ");
}

// テスト
try {
    safeProcessValues("hello", true);  // ここでエラーが発生するはず
} catch (error) {
    console.log("Error caught as expected");
}

このテストでは、指定された型以外のデータが渡された場合にエラーが発生することを確認します。

デバッグの手法

関数が正しく動作しない場合、デバッグを通じて問題の原因を特定し修正する必要があります。デバッグには以下のような手法を用います。

コンソールログを使ったデバッグ

console.logを使って関数内部の動作を追跡し、どの時点で想定外の動作が起こっているかを調べます。特に、ユニオン型の分岐処理や可変長引数の処理で、型が正しく判断されているかを確認します。

function debugProcessValues(...values: (string | number)[]): void {
    values.forEach(value => {
        console.log(`Processing value: ${value}, type: ${typeof value}`);
    });
}

debugProcessValues("text", 42, true); // ここで型情報を出力して確認

このように、処理されている引数の型や値をログに出力することで、バグの原因を特定できます。

型アサーションの活用

TypeScriptでは型アサーション(asキーワード)を使って、特定の型であることを明示的に指定し、デバッグ時に型の誤りを早期に発見することができます。

function processAndAssert(...values: (string | number)[]): void {
    values.forEach(value => {
        if (typeof value === 'string') {
            console.log((value as string).toUpperCase());
        } else {
            console.log((value as number).toFixed(2));
        }
    });
}

型アサーションを使うことで、特定の型であることを強調し、型が不一致であればデバッグが容易になります。

開発ツールのデバッグ機能

TypeScriptコードをデバッグする際には、ブラウザの開発者ツールやVSCodeのデバッガ機能を使うと効率的です。ブレークポイントを設定し、ステップごとに関数の実行状態を確認することで、処理の流れや変数の値を詳細にチェックできます。

テストフレームワークの活用

単体テストを自動化するために、TypeScript向けのテストフレームワーク(例えばJestMocha)を活用することも推奨されます。テストフレームワークを使うことで、テストの実行や結果の確認が容易になり、時間をかけずにコードの動作確認ができます。

test('processValues should handle mixed types', () => {
    expect(processValues("text", 42)).toBe("String: text, Number: 42");
});

これにより、複数のケースを効率よくテストし、コードの信頼性を高めることができます。

テストとデバッグは、ユニオン型や可変長引数を使用する複雑な関数であっても、問題の早期発見と解決を促進します。適切なテストケースを作成し、デバッグを繰り返すことで、堅牢なプログラムを構築することが可能です。

応用例: 複雑な関数定義

ユニオン型と可変長引数を組み合わせることで、より複雑で高度な関数を定義することができます。特に、異なるデータ型やオプションの引数が混在する場合、これらの技術を適用することで、柔軟で拡張性の高い関数を作成できます。ここでは、複雑なユニオン型と可変長引数を使用した応用例を紹介し、TypeScriptを活用した高機能な関数の設計方法を解説します。

応用例1: 複数のデータ型を受け取る演算関数

複数の異なるデータ型を受け取り、それらを柔軟に処理する関数を考えてみましょう。例えば、数値と文字列を同時に受け取り、数値は合計し、文字列は連結する関数を作成することができます。このような関数は、データの種類ごとに異なる処理を行う必要があるため、ユニオン型と可変長引数を活用することで柔軟な設計が可能です。

function processMixedData(...inputs: (string | number)[]): { sum: number, concatenated: string } {
    let sum = 0;
    let concatenated = "";

    inputs.forEach(input => {
        if (typeof input === 'number') {
            sum += input;
        } else {
            concatenated += input;
        }
    });

    return { sum, concatenated };
}

// 使用例
const result = processMixedData(10, "apple", 20, "banana", 30);
console.log(result); // { sum: 60, concatenated: "applebanana" }

この関数では、number型の値は合計され、string型の値は連結されます。ユニオン型を使うことで、異なる型を柔軟に処理し、結果をまとめて返すことができます。

応用例2: オプション引数を含むAPIリクエスト関数

ユニオン型と可変長引数は、REST APIのリクエストを送信する関数でも活用できます。たとえば、オプションのクエリパラメータやヘッダーを柔軟に扱う関数を作成することで、APIのエンドポイントに応じた柔軟なリクエストを実現できます。

type RequestOptions = { method: "GET" | "POST", headers?: Record<string, string>, body?: string };

function sendRequest(url: string, ...options: RequestOptions[]): void {
    const defaultOptions: RequestOptions = { method: "GET" };

    const finalOptions = Object.assign({}, defaultOptions, ...options);

    console.log(`Sending request to ${url} with options:`, finalOptions);
}

// 使用例
sendRequest("https://api.example.com/data", { method: "POST", headers: { "Content-Type": "application/json" }, body: '{"key":"value"}' });

この関数では、可変長引数を使用して複数のオプションを渡し、デフォルトのオプションとマージすることができます。ユニオン型によって、GETPOSTなどの特定のHTTPメソッドのみを許可し、型安全性を確保しています。

応用例3: 複雑なフィルタリング関数

ユニオン型を使って、複数の異なるフィルタ条件を受け取る関数を作成することもできます。たとえば、配列内のデータをstringnumberのフィルタ条件に基づいて動的にフィルタリングする関数を作成できます。

function filterData<T extends string | number>(data: T[], ...filters: (T | ((item: T) => boolean))[]): T[] {
    return data.filter(item => {
        return filters.every(filter => {
            if (typeof filter === 'function') {
                return filter(item);
            } else {
                return item === filter;
            }
        });
    });
}

// 使用例
const numbers = [1, 2, 3, 4, 5, 6];
const filteredNumbers = filterData(numbers, 2, (num) => num > 4);
console.log(filteredNumbers); // [5, 6]

この関数では、単純な値によるフィルタだけでなく、関数による動的なフィルタも適用できます。これにより、複数の条件に基づいてデータを柔軟に処理できる汎用的な関数を構築できます。

応用例4: ユニオン型を使ったイベントリスナ関数

Webアプリケーション開発では、異なるイベントに応じた処理を行うイベントリスナを実装することがよくあります。ユニオン型を使用して、イベントの種類ごとに異なる処理を行うリスナ関数を定義することができます。

type Event = { type: "click" | "hover", payload?: any };

function handleEvent(...events: Event[]): void {
    events.forEach(event => {
        switch (event.type) {
            case "click":
                console.log("Clicked!", event.payload);
                break;
            case "hover":
                console.log("Hovered!", event.payload);
                break;
        }
    });
}

// 使用例
handleEvent({ type: "click", payload: { x: 100, y: 200 } }, { type: "hover", payload: "menu" });

この関数では、クリックイベントやホバーイベントに応じた処理を分岐させ、ユニオン型を使用して型安全にイベントを扱います。各イベントに異なるペイロード(データ)を渡すことも可能です。

ユニオン型と可変長引数の高度な活用

これらの応用例からも分かるように、ユニオン型と可変長引数を組み合わせることで、複雑で柔軟な関数を構築できます。ユニオン型によって異なる型を安全に扱いながら、可変長引数を活用することで、さまざまなデータやオプションを効率的に処理できるようになります。特に、複数の入力データや動的なフィルタリング、オプション設定を扱う際に非常に有用です。

TypeScriptのこれらの機能をうまく活用することで、拡張性が高く、堅牢なコードを作成できるようになります。

演習問題: ユニオン型と可変長引数

ユニオン型と可変長引数を使った関数の理解を深めるために、いくつかの演習問題を用意しました。これらの問題に取り組むことで、実際にコードを書きながら学習を進めることができます。

問題1: 複数の引数を受け取って処理する関数

課題: ユニオン型を使用して、stringまたはnumberの引数を複数受け取り、それぞれの型に応じて処理を行う関数handleInputsを作成してください。文字列は大文字に変換し、数値は2倍にした値を返すようにします。

function handleInputs(...inputs: (string | number)[]): (string | number)[] {
    // ここにコードを記述してください
}

期待される結果

console.log(handleInputs("hello", 10, "world", 5)); 
// 出力: ["HELLO", 20, "WORLD", 10]

問題2: 可変長引数でフィルタリングを行う関数

課題: number型の引数を複数受け取り、それらの中から指定された数値より大きいものだけを返す関数filterNumbersを作成してください。最初の引数は基準値とし、それ以降の引数はフィルタ対象とします。

function filterNumbers(threshold: number, ...numbers: number[]): number[] {
    // ここにコードを記述してください
}

期待される結果

console.log(filterNumbers(10, 5, 15, 20, 8, 12)); 
// 出力: [15, 20, 12]

問題3: 動的な型判定を行う関数

課題: ユニオン型を使用して、異なる型の引数を受け取り、それぞれの型に応じた適切な処理を行う関数dynamicProcessorを作成してください。この関数は、string型なら文字数、number型なら値を2乗した結果を返し、それ以外の型は「Unknown type」と表示するようにしてください。

function dynamicProcessor(...inputs: (string | number | boolean)[]): (number | string)[] {
    // ここにコードを記述してください
}

期待される結果

console.log(dynamicProcessor("typescript", 7, false, "code", 3)); 
// 出力: [10, 49, "Unknown type", 4, 9]

問題4: APIオプションを扱う関数

課題: REST APIリクエストを行う関数requestAPIを作成してください。この関数は、URLとHTTPメソッド(”GET”または”POST”)を必須の引数とし、オプションでヘッダーやボディを追加できるようにします。

type RequestOptions = { headers?: Record<string, string>, body?: string };

function requestAPI(url: string, method: "GET" | "POST", ...options: RequestOptions[]): void {
    // ここにコードを記述してください
}

期待される結果

requestAPI("https://example.com", "POST", { headers: { "Content-Type": "application/json" }, body: '{"key":"value"}' });
// 出力: URL: https://example.com, Method: POST, Headers: { "Content-Type": "application/json" }, Body: {"key":"value"}

問題5: データのグループ化

課題: stringnumberの配列を受け取り、文字列は連結、数値は合計する関数groupDataを作成してください。結果はオブジェクトで返すようにします。

function groupData(...inputs: (string | number)[]): { total: number, concatenated: string } {
    // ここにコードを記述してください
}

期待される結果

console.log(groupData("apple", 10, "banana", 20, "cherry", 30)); 
// 出力: { total: 60, concatenated: "applebananacherry" }

これらの演習問題を解くことで、ユニオン型と可変長引数を使った関数の設計や実装に関するスキルを向上させることができます。挑戦してみてください!

まとめ

本記事では、TypeScriptにおけるユニオン型と可変長引数を組み合わせた関数定義の方法について解説しました。ユニオン型を使うことで、複数の異なる型を柔軟に扱える関数を作成でき、可変長引数を組み合わせることで、引数の数を限定せずに動的な処理が可能となります。また、型安全性を保ちながらエラーハンドリングやテストを適切に行うことで、堅牢で拡張性の高いコードが書けるようになります。

これらの技術を活用することで、さまざまなシチュエーションに応じた効率的で柔軟な関数を実装でき、実際の開発プロジェクトで大いに役立つでしょう。

コメント

コメントする

目次