programowanieArchitekt TypeScript

Jak realizuje się dopasowanie wzorca (pattern matching) w TypeScript za pomocą unions dyskryminowanych? Jak prawidłowo strukturyzować typy i jakie pułapki mogą wystąpić?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

W TypeScript dopasowanie wzorca realizuje się poprzez "unions dyskryminowane" (discriminated unions). Każdemu obiektowi w unii przypisuje się obowiązkowe pole-dyskryminator (zwykle ciąg znaków, na przykład type), według którego TypeScript rozróżnia warianty.

Przykład:

type Success = { type: 'success'; data: string }; type Failure = { type: 'failure'; error: string }; type Result = Success | Failure; function handleResult(result: Result) { switch (result.type) { case 'success': // result: Success console.log(result.data); break; case 'failure': // result: Failure console.error(result.error); break; } }

W switch/case lub if po polu-dyskryminatorze TypeScript "zawęża" typ dokładnie do odpowiedniego wariantu.

Główne zalety:

  • Ścisłe typowanie — nie można odwołać się do nieistniejącego pola.
  • Sprawdzanie exhaustiveness — jeśli nie obsłużysz wszystkich wariantów, czasami występuje błąd (można to wymusić).

Pytanie z pułapką

Jeśli dodasz nowy wariant do unii dyskryminowanej, czy TypeScript wymusi aktualizację wszystkich switch-case, aby obsłużyć nowy wariant?

Odpowiedź: Nie, tylko jeśli jawnie dodasz obsługę "niemożliwego" wariantu. Na przykład używając funkcji never:

Przykład:

function assertNever(x: never): never { throw new Error('Unexpected variant: ' + x); } function handle(r: Result) { switch(r.type) { case 'success': /* ... */; break; case 'failure': /* ... */; break; default: return assertNever(r); // TS zgłosi błąd, jeśli pojawi się nowy typ } }

Przykłady rzeczywistych błędów z powodu nieznajomości niuansów tematu.


Historia

Po rozszerzeniu typu "Result" o nowy wariant ('pending') w kilku miejscach aplikacji stare switch-case nie obsłużyły tego przypadku. W rezultacie część interfejsów przestała działać. Błąd został zauważony dopiero w produkcji tydzień po wydaniu.


Historia

Próba użycia unii dyskryminowanej bez unikalnego dyskryminatora (pole type powtarzało się w dwóch typach) prowadziła do "rozmycia" typów: TypeScript przestał dokładnie zawężać typ i stało się możliwe odwołanie się do nieistniejących pól bez błędu kompilacji. Kilka krytycznych błędów trafiło do produkcji.


Historia

W projekcie dopasowanie wzorca zrealizowano poprzez if-else w oparciu o kilka pól zamiast używania jednego jawnego dyskryminatora. Utrudniło to przejście na sprawdzanie exhaustiveness z funkcją never i skomplikowało czytelność kodu — switch-case działały nieprawidłowo, a nowe warianty "łamały" istniejącą logikę.