関数のオーバーロードは、異なるパラメータと返り値のバリエーションを持つ一つの関数を作成することを可能にします。他の言語、例えばC#やJavaでは、異なるタイプや引数の数を持つ同じ名前の関数を複数記述できます。TypeScriptでは、型レベルでのシグネチャーのオーバーロードを通じてこのメカニズムに近づけますが、関数の本体は常に一つです。
問題の歴史は、JavaScriptにはタイプや引数の数によるオーバーロードに対するネイティブサポートがないことに起因します。TypeScriptの登場により、関数の複数のシグネチャーを連続して宣言し、内部で手動でどのタイプを扱っているのかを区別することでオーバーロードがエミュレーションされます。
問題は、宣言されたシグネチャーとの一致を監視しないと発生します:関数本体内のパラメータにTypeScriptは最も一般的な型(全パラメータ型の合併)を割り当て、プログラマーはチェックやタイプガードを行う必要があります。
解決策:明示的なシグネチャーでオーバーロードを厳密に型付けし、ユニオンタイプやタイプガードを適切に使用し、動作を適切に文書化することです。
コードの例:
function format(value: string): string; function format(value: number, locale: string): string; function format(value: any, locale?: string): string { if (typeof value === 'number') { return value.toLocaleString(locale); } return value.trim(); } format(' hello '); // 'hello' format(123456, 'ru-RU'); // '123 456'
主な特徴:
C#のように異なる本体を持つ同名の関数を複数宣言できますか?
いいえ。TypeScript(およびJavaScript)では、実際にこの名前を持つ関数は一つだけです。オーバーロードは型レベルでコンパイラのためにのみ機能し、関数の本体は一つしかありません。
オーバーロードシグネチャーの後に最も一般的なパラメータのシグネチャーを実装しなかった場合、どうなりますか?
TypeScriptはコンパイルエラーを出します。常にすべてのオーバーロードの可能なパターンをカバーする一つの実装関数が必要です。
function foo(a: string): string; function foo(a: number): number; // 本体なし — エラー
本体内で特定のシグネチャーに特有のプロパティを使用できますか?
いいえ、タイプガード(type guard)または型アサーションの後でのみ可能です。そうでなければ、TypeScriptはこれらのプロパティを直接使用することを許可しません。現在どのシグネチャーが呼び出されているのかが不明だからです。
function bar(x: string): number; function bar(x: number): number; function bar(x: string | number): number { if (typeof x === 'string') return x.length; return x * 2; }
ユニバーサルシグネチャーと実装を追加するのを忘れました:
function sum(a: string, b: string): string; function sum(a: number, b: number): number; // 実装なし — コンパイラがエラー
利点:
欠点:
標準に従ってオーバーロードを実装します:
function sum(a: string, b: string): string; function sum(a: number, b: number): number; function sum(a: any, b: any): any { return typeof a === 'string' && typeof b === 'string' ? a + b : a + b; }
利点:
欠点: