programowanieProgramista TypeScript

Jak zaimplementować i poprawnie typować przeciążenie funkcji w TypeScript na poziomie sygnatur? Dlaczego nie można używać kilku ciał funkcji i co się dzieje z typami parametrów wewnątrz ciała funkcji? Podaj praktyczne scenariusze użycia oraz najczęstsze błędy.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Przeciążenie funkcji pozwala stworzyć jedną funkcję z różnymi wariantami przyjmowanych parametrów i zwracanych wartości. W innych językach, takich jak C# czy Java, można opisać kilka funkcji o tej samej nazwie, ale z różnymi typami lub liczbą argumentów. TypeScript zbliża ten mechanizm poprzez przeciążenie sygnatur na poziomie typów, ale ciało funkcji jest zawsze jedno.

Historia pytania — w JavaScript nie ma natywnego wsparcia dla przeciążenia według typu lub liczby parametrów. Wraz z pojawieniem się TypeScript przeciążenie jest emulowane poprzez zadeklarowanie kilku sygnatur funkcji z rzędu i jednej realizacji, wewnątrz której ręcznie odróżnia się, z jakim typem aktualnie pracujemy.

Problem pojawia się, gdy nie dba się o zgodność ze stwierdzonymi sygnaturami: parametrom wewnątrz ciała funkcji TypeScript przypisuje najbardziej ogólny typ (połączenie wszystkich typów parametrów) i programista musi przeprowadzać sprawdzenia oraz type guards.

Rozwiązanie: Ściśle typować przeciążenia w jawnych sygnaturach, mądrze korzystać z union types i type guards, poprawnie dokumentować zachowanie.

Przykład kodu:

function format(value: string): string; function format(value: number, locale: string): string; function format(value: any, locale?: string): string { if (typeof value === 'number') { return value.toLocaleString(locale); } return value.trim(); } format(' hello '); // 'hello' format(123456, 'ru-RU'); // '123 456'

Kluczowe cechy:

  • Kilka służbowych przeciążających sygnatur z rzędu, jedna realizacja pod nimi.
  • Do pracy z parametrami w ciele używają albo union-typ, albo any/unknown.
  • Typ samego ciała jest szerszy/uniwersalniejszy niż sygnatury powyżej.

Pytania zaczepne.

Czy można zadeklarować kilka funkcji o tej samej nazwie i różnych ciałach, jak w C#?

Nie. W TypeScript (i w JavaScript) faktycznie istnieje tylko jedna funkcja o tej nazwie. Przeciżenia działają tylko na poziomie typów dla kompilatora, ale istnieje tylko jedno ciało funkcji.

Co się stanie, jeśli nie zrealizujesz sygnatury z najbardziej ogólnymi parametrami po przeciążających sygnaturach?

TypeScript zgłosi błąd kompilacji. Zawsze powinna być jedna funkcja-realizacja, której parametry pokrywają wszystkie możliwe warianty przeciżeń.

function foo(a: string): string; function foo(a: number): number; // brak ciała — błąd

Czy mogę wewnątrz ciała używać właściwości specyficznych dla jednej konkretnej sygnatury?

Nie, tylko po sprawdzeniu typu (type guard) lub rzutowaniu typu. W przeciwnym razie TypeScript nie pozwoli używać tych właściwości bezpośrednio, ponieważ nie wiadomo, która sygnatura jest aktualnie wywoływana.

function bar(x: string): number; function bar(x: number): number; function bar(x: string | number): number { if (typeof x === 'string') return x.length; return x * 2; }

Typowe błędy i antywzorce

  • Nie dodają realizacji po przeciążających sygnaturach — kompilacja się nie powiedzie.
  • W ciele funkcji używają specyficznych właściwości bez sprawdzenia typu.
  • Zbyt komplikują przeciżenia, prowadząc do kodu, który jest trudny do odczytania.
  • Nie opisują wyraźnie zwracanych typów lub zapominają aktualizować sygnatury przy zmianach logiki funkcji.

Przykład z życia

Negatywny przypadek

Zapomnieli dodać uniwersalną sygnaturę-realizację:

function sum(a: string, b: string): string; function sum(a: number, b: number): number; // brak realizacji — kompilator się skarży

Zalety:

  • Pomysł na opisanie wygodnego API

Wady:

  • Kod nie kompiluje się, niemożliwe jest przetworzenie żadnej opcji

Pozytywny przypadek

Realizują przeciążenie zgodnie z normą:

function sum(a: string, b: string): string; function sum(a: number, b: number): number; function sum(a: any, b: any): any { return typeof a === 'string' && typeof b === 'string' ? a + b : a + b; }

Zalety:

  • Wszystkie warianty są poprawnie przetwarzane, typy są wnioskowane na etapie kompilacji

Wady:

  • Ciało funkcji wymaga ręcznej kontroli typów, nie można zapomnieć o sprawdzeniu każdego wariantu