はじめに
TypeScript のオーバーロードには、大きく分けて以下の 2 つの書き方があります。
function
を利用する-
interface
やtype
に呼び出し可能オブジェクト(Callable)を定義する
それぞれの使い方についてまとめてみました。
また、オーバーロード使用時の注意点もまとめてみたので、見ていただけると嬉しいです!
環境
TypeScript: v4.1.3
オーバーロードについて
最初に、TypeScript におけるオーバーロードについて軽く触れておきます。
既にご存知の方は読み飛ばしてください。
TypeScript は JavaScript のスーパーセットですが、元々の Javascript にはオーバーロードと呼ばれる機能はありません。
以下のように、関数の中でtypeof
を用いて引数の場合分けを行い、擬似的なオーバーロードを実装することは可能です。
しかし、他言語のように引数の異なる同名のメソッドを定義することはできません。
// 第一引数がstring型の場合は NaN という文字列を返す
// 第二引数が指定されている場合は、第一引数と合算した結果を返す
// 第一引数のみが指定されている場合は、インクリメントした結果を返す
function increment(val, added) {
if (typeof val === "string") {
return "NaN";
} else if (typeof val === "number") {
if (typeof added !== "undefined") {
return val + added;
}
return ++val;
}
}
これを TypeScript で書き直すと以下のようになります。
// increment(
// val: string | number,
// added?: number | undefined
// ) : number | "NaN" | undefined
function increment(val: string | number, added?: number) {
if (typeof val === "string") {
return "NaN";
} else if (typeof val === "number") {
if (typeof added !== "undefined") {
return val + added;
}
return ++val;
}
}
// const result1: string | number
const result1 = increment("1"); // NaN
// const result2: string | number
const result2 = increment(1); // 2
// const result3: string | number
const result3 = increment(1, 2); // 3
この状態でも動作はします。
しかしこれでは、オーバーロードのメリットである入力する型によって出力する型を切り替える
ことができません。
(引数がなんであれ、返り値の型はstring | number
となります)
入力する型によって出力する型を切り替える
ために、以下のように実行する関数の前にオーバーロードで使用する型情報を記述してあげます。
function increment(str: string): string;
function increment(num: number): number;
function increment(num: number, added: number): number;
function increment(val: string | number, added?: number): string | number {
if (typeof val === "string") {
return "NaN";
} else if (typeof val === "number") {
if (typeof added !== "undefined") {
return val + added;
}
return ++val;
}
return val;
}
// const result1: string
const result1 = increment("1"); // NaN
// const result2: number
const result2 = increment(1); // 2
// const result3: number
const result3 = increment(1, 2); // 3
こうすることで、他言語と同じように入力する型によって出力する型を切り替える
ことができます。
使い方
function
を利用する
基本の書き方は上で記述した通りになります。
具体的には、
- 実行する関数の前に、
function ${実行関数と同名} (${引数のシグネチャ}): ${返り値のシグネチャ}
を定義する - 実行する関数として、1.で定義したシグネチャをすべて受け取ることが可能な関数を定義する。この例の場合、
- 第一引数は
string
とnumber
の可能性があるためstring | number
とする - 第一引数は
number
とundefined
(定義無し)の可能性があるため、オプショナル(?
)とする - 返り値は
string
とnumber
の可能性があるためstring | number
とする
- 第一引数は
もし、クラスメソッドとしてオーバーロードメソッドを定義する場合は、以下のようになります。
class Sample {
increment(str: string): string;
increment(num: number): number;
increment(num: number, added: number): number;
increment(val: string | number, added?: number): string | number {
if (typeof val === "string") {
return "NaN";
} else if (typeof val === "number") {
if (typeof added !== "undefined") {
return val + added;
}
return ++val;
}
return val;
}
}
const sample = new Sample();
// const result1: string
const result1 = sample.increment("1"); // NaN
// const result2: number
const result2 = sample.increment(1); // 2
// const result3: number
const result3 = sample.increment(1, 2); // 3
ちなみに、この記述方法はアロー関数では使用できません。
interface
や type
に呼び出し可能オブジェクトを定義する
呼び出し可能オブジェクトとは、上でも使用した(${引数の型}): ${返り値の型}
で表記されるシグネチャのことです。
これを、interface
や type
の中で定義することで、オーバーロードを使用することができます。
type Increment = {
(str: string): string;
(num: number): number;
(num: number, added: number): number;
};
const increment: Increment = (val: string | number, added?: number): any => {
if (typeof val === "string") {
return "NaN";
} else if (typeof val === "number") {
if (typeof added !== "undefined") {
return val + added;
}
return ++val;
}
return val;
};
// const result1: string
const result1 = increment("1"); // NaN
// const result2: number
const result2 = increment(1); // 2
// const result3: number
const result3 = increment(1, 2); // 3
ちなみに、関数の宣言方法には 2 種類があります。
普段使用するのはShortHand
かと思いますが、これはLongHand
の書き方を省略したものとなります。
上記で使用したようにLongHand
はオーバーロードで使用できますが、ShortHand
ではできません。
type LongHand = {
(a: number): number;
};
type ShortHand = (a: number) => number;
参考: 関数の宣言