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:
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 } }
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ę.