Перегрузки нужны в следующих случаях:
1. Когда от типа некоторого аргумента зависит тип возвращаемого значения;
2. Когда от типа некоторого аргумента зависят типы последующих аргументов;
3. Комбинация из 1 и 2 вариантов.
Важно понимать, что в TS перегрузки существуют только на уровне типов, в отличии от других языков. Притом сами перегрузки реализованы как
intersection от всех сигнатур, что порой вызывает проблемы с присвоением функции к типу с перегруженной сигнатурой. Все дело в
вариантности, так как аргументы функции контравариантны сигнатуре самой функции, когда большинство отношений типов (и intersection в том числе) в TS ковариантны. В вашем примере с SumOrConcat эту проблему решили через тип any, который делает любую композицию типов с ним бивариантной.
На самом деле в примере с SumOrConcat вполне можно задать типы аргументам:
type SumOrConcat = {
(a: number, b: number): number
(a: string, b: number): string
}
const result: SumOrConcat = (a: number | string, b: number): any => {
if (typeof a === 'number') {
return a + b
}
return `${a} ${b}`
}
Но надежнее все же так:
type SumOrConcat = {
(a: number, b: number): number
(a: string, b: number): string
}
const result = ((a: number | string, b: number): number | string => {
if (typeof a === 'number') {
return a + b
}
return `${a} ${b}`
}) as SumOrConcat;
Ну и надо отметить, что синтаксис самих перегрузок таких проблем не имеет, хотя стрелочную функцию с ним не опишешь
function sumOrConcat(a: number, b: number): number;
function sumOrConcat(a: string, b: number): string;
function sumOrConcat(a: number | string, b: number): number | string {
if (typeof a === 'number') {
return a + b
}
return `${a} ${b}`
}
const result = sumOrConcat;
Так же, очень часто можно обойтись без перегрузки используя дженерики. Сам такой код будет выглядеть сложнее, но пользоваться им будет проще.
Пример.