11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【TypeScript】返り値の異なるオーバーロードを定義する場合、返り値の型を`any`にする必要がある

Posted at

はじめに

昨日、TypeScript のオーバーロードの様々な書き方についてまとめてみました。
本日は、オーバーロードの挙動確認中に遭遇した不思議な仕様について紹介します。

環境

TypeScript: v4.1.3

コード

Playground Link

結論

以下のように返り値の異なるオーバーロードを定義する場合、実装関数の返り値の型が union type (number | string)だと、Type 'string' is not assignable to type 'number'というエラーが表示されます。

type T = {
  (val: number): number;
  (val: string): string;
};

// Type 'string' is not assignable to type 'number'
const fn: T = (val: number | string) => val;

そのため、実装関数の返り値の型を any にする必要があります。

// No Error
const fn: T = (val: number | string): any => val;

// const num: number
const num = fn(1);
// const str: string
const str = fn("1");

詳細

具体例を見てみましょう。
以下で定義されているTは、引数にstring | numberを受け取り、受け取った引数をそのまま返す関数になります。
当然、返り値の型はstring | numberとなります。

// const fn: (val: number | string) => string | number
const fn = (val: number | string) => val;

// const num: string | number
const num = fn(1);
// const str: string | number
const str = fn("1");

この関数にオーバーロードを使用して、以下のような型を返すようにします。

  • 引数の型がstringであれば、返り値の型はstring
  • 引数の型がnumberであれば、返り値の型はnumber

そうすると以下のようになります。
しかし、このように定義した場合、以下に記述したようなエラーが表示されます。

type T = {
  (val: number): number;
  (val: string): string;
};

// Error
// Type '(val: number | string) => string | number' is not assignable to type 'T'.
//   Type 'string | number' is not assignable to type 'number'.
//     Type 'string' is not assignable to type 'number'.ts(2322)
const fn: T = (val: number | string) => val;

// const num: number
const num = fn(1);
// const str: string
const str = fn("1");

これを解消するには、返り値の型をanyにする必要があります。

type T = {
  (val: number): number;
  (val: string): string;
};

// No Error
const fn: T = (val: number | string): any => val;
// or const fn: T = (val: number | string) => val as any;

// const num: number
const num = fn(1);
// const str: string
const str = fn("1");

TypeScript のオーバーロードの仕様上、オーバーロードを使用している関数 (この場合はfn)は、それぞれの型を同時に満たす必要があります。

const num: number = fn(1);
const str: string = fn("1");

つまり、引数の型はnumber | string である必要があり、返り値の型はnumber & stringである必要があるようです。
参考: Overloading arrow function return type should allow union type

実際、以下のようにfnを定義すると、エラーが消えます。

const fn: T = (val: number | string): number & string => val as never;

また、この挙動は呼び出し可能オブジェクト(Callable)を用いてオーバーロードを定義した場合のみ発生し、functionを用いてオーバーロードした場合は発生しません。

function fn(val: number): number;
function fn(val: string): string;
function fn(val: number | string) {
  return val;
}

// const num: number
const num = fn(1);
// const str: string
const str = fn("1");

参考

11
5
1

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
11
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?