ProgrammingTypeScript開発者

TypeScriptでの関数のオーバーロードをシグネチャーレベルで実装し、正しく型付けするにはどうすればよいですか?なぜ複数の関数本体を使用できないのか、そして関数本体内のパラメータの型に何が起こるのかを教えてください。実用的な使用シナリオと最も一般的なエラーを示してください。

Hintsage AIアシスタントで面接を突破

回答。

関数のオーバーロードは、異なるパラメータと返り値のバリエーションを持つ一つの関数を作成することを可能にします。他の言語、例えば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'

主な特徴:

  • 複数のオーバーロードシグネチャを連続して宣言し、それに対する一つの実装。
  • 本体内でパラメータを扱う際には、ユニオン型かany/unknownを使用します。
  • 本体の型は上のシグネチャーよりも広い/普遍的です。

落とし穴のある質問。

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; // 実装なし — コンパイラがエラー

利点:

  • 便利なAPIのアイデア

欠点:

  • コードがコンパイルされず、どのバリエーションも処理できない

ポジティブケース

標準に従ってオーバーロードを実装します:

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; }

利点:

  • すべてのバリエーションが正しく処理され、型はコンパイル時に推論されます。

欠点:

  • 関数の本体で型の手動管理が必要であり、各バリエーションをチェックするのを忘れないようにする必要があります。