Przeciążenia funkcji

Kilka sygnatur jednej funkcji: typ wyniku zależy od tego, z jakimi argumentami ją wywołasz.

Na tej stronie

Przykład

#
TypeScript
function toPoints(value: string): number
function toPoints(values: string[]): number[]
function toPoints(input: string | string[]): number | number[] {
  if (Array.isArray(input)) return input.map((item) => Number.parseInt(item, 10))
  return Number.parseInt(input, 10)
}

const single = toPoints('120')
// const single: number
const many = toPoints(['40', '75', '90'])
// const many: number[]

console.log(single + 30)
console.log(many)
Wynik
150
[40, 75, 90]

Definicja i zastosowanie

#

Przeciążenia opisują jedną funkcję kilkoma sygnaturami. Najpierw wypisujesz same nagłówki, bez ciała, a bezpośrednio pod nimi jedną implementację. Przy wywołaniu kompilator sprawdza nagłówki od góry i wybiera pierwszy, który pasuje do argumentów. Dzięki temu dla napisu funkcja może zwracać liczbę, a dla tablicy napisów tablicę liczb.

Sygnatura implementacji jest ukryta: z zewnątrz widać tylko przeciążenia. Implementacja musi obsłużyć wszystkie warianty, więc zwykle przyjmuje unię typów i sama zawęża ją w środku, np. przez Array.isArray() albo typeof. Jeśli któryś nagłówek do niej nie pasuje, kompilator zgłosi „This overload signature is not compatible with its implementation signature”.

Przeciążać można też metody klas, a w interfejsach wystarczy zapisać kilka sygnatur wywołania. Zanim jednak sięgniesz po przeciążenia, sprawdź, czy nie wystarczy unia, parametr opcjonalny albo generyk. Przeciążenia opłacają się wtedy, gdy typ wyniku zależy od argumentów albo gdy warianty różnią się liczbą i znaczeniem parametrów.

Składnia

#
Składnia
function nazwa(a: TypA): WynikA
function nazwa(a: TypB): WynikB
function nazwa(a: TypA | TypB): WynikA | WynikB {
  // implementacja
}

Więcej przykładów

#
Różna liczba argumentów
TypeScript
function makeRange(end: number): number[]
function makeRange(start: number, end: number): number[]
function makeRange(a: number, b?: number): number[] {
  const [start, end] = b === undefined ? [0, a] : [a, b]
  return Array.from({ length: end - start }, (_, i) => start + i)
}

console.log(makeRange(4))
console.log(makeRange(2, 5))

makeRange(1, 2, 3)
// Błąd: Expected 1-2 arguments, but got 3.
Wynik
[0, 1, 2, 3]
[2, 3, 4]
Wywołanie, które nie pasuje do żadnej sygnatury
TypeScript
toPoints(42)
// Błąd: No overload matches this call.
//   Overload 1 of 2, '(value: string): number', gave the following error.
//     Argument of type 'number' is not assignable to parameter of type 'string'.
//   Overload 2 of 2, '(values: string[]): number[]', gave the following error.
//     Argument of type 'number' is not assignable to parameter of type 'string[]'.

Dobre praktyki

#
  • Układaj przeciążenia od najbardziej szczegółowego do najogólniejszego. Kompilator bierze pierwsze pasujące, więc ogólna sygnatura na górze przesłoniłaby pozostałe.
  • Argument typu unii, np. string | string[], nie pasuje do żadnego z przeciążeń z przykładu, bo sygnatura implementacji jest niewidoczna. Jeśli takie wywołania są potrzebne, dodaj osobne przeciążenie przyjmujące unię.
  • Gdy wszystkie warianty zwracają ten sam typ i różnią się tylko typem parametru, zastąp przeciążenia jedną sygnaturą z unią. Kod będzie krótszy, a podpowiedzi w edytorze prostsze.

Powiązane hasła

#

Widzisz błąd albo brakuje przykładu? Napisz do nas.