LoginSignup
45
30

More than 3 years have passed since last update.

【TypeScript】オーバーロードの様々な書き方

Last updated at Posted at 2021-01-01

はじめに

TypeScript のオーバーロードには、大きく分けて以下の 2 つの書き方があります。

それぞれの使い方についてまとめてみました。

また、オーバーロード使用時の注意点もまとめてみたので、見ていただけると嬉しいです!

環境

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

Playground Link

こうすることで、他言語と同じように入力する型によって出力する型を切り替えることができます。

使い方

function を利用する

基本の書き方は上で記述した通りになります。
具体的には、

  1. 実行する関数の前に、function ${実行関数と同名} (${引数のシグネチャ}): ${返り値のシグネチャ} を定義する
  2. 実行する関数として、1.で定義したシグネチャをすべて受け取ることが可能な関数を定義する。この例の場合、
    • 第一引数はstringnumberの可能性があるためstring | numberとする
    • 第一引数はnumberundefined(定義無し)の可能性があるため、オプショナル(?)とする
    • 返り値はstringnumberの可能性があるため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

ちなみに、この記述方法はアロー関数では使用できません。

interfacetype に呼び出し可能オブジェクトを定義する

呼び出し可能オブジェクトとは、上でも使用した(${引数の型}): ${返り値の型}で表記されるシグネチャのことです。
これを、interfacetypeの中で定義することで、オーバーロードを使用することができます。

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;

参考: 関数の宣言

参考

45
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
30