Зачем нужна перегрузка функций?

Не очень понимаю целесообразность использования перегрузки.
В учебнике сказано, мол, это нужно для того, чтобы функция могла принимать разные типы данных и возвращать разные типы данных. И приводится такой пример:
type Reserve = {
  (from: Date, to: Date, destination: string): string
  (from: Date, destination: string)
}

const reserve: Reserve = (
  from: Date,
  toOrDestination: Date | string,
  destination?: string
) => {
  if (toOrDestination instanceof Date && destination !== undefined) {
    return 'Round way ticket'
  } else if (typeof toOrDestination === 'string') {
    return 'One way ticket'
  }
}


Это типа заказ билетов с возможностью не брать обратный билет. Но разве эта простыня лучше, скажем, такой записи?
type Reserve2 = (from: Date, destination: string, to?: Date) => string

const reserve2: Reserve2 = (from, dest, to) => (
  !to ? 'One way ticket' : 'Round way ticket'
)


В другом месте видел такой пример, дескать, 'a' может быть как числом, так и строкой
type SumOrConcat = {
  (a: number, b: number): number
  (a: string, b: number): string
}

const result: SumOrConcat = (a: any, b: any): any => {
  if (typeof a === 'number') {
    return a + b
  }

  return `${a} ${b}`
}


Но это же совсем дичь, да еще и с использованием any. Разве не лучше сделать вот так?
type SumOrConcat = (a: number | string, b: number) => number | string

const result: SumOrConcat = (a, b) => (
  typeof a === 'number' ? a + b : `${a} ${b}`
)


Придумывал сам еще всякие примеры, но везде получается, что можно сделать без перегрузок. Какие могут быть ситуации, где перегрузка реально необходима?
  • Вопрос задан
  • 428 просмотров
Решения вопроса 1
bingo347
@bingo347 Куратор тега TypeScript
Crazy on performance...
Перегрузки нужны в следующих случаях:
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;


Так же, очень часто можно обойтись без перегрузки используя дженерики. Сам такой код будет выглядеть сложнее, но пользоваться им будет проще. Пример.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы