Wzorce
Jak dostać błąd kompilacji, gdy ktoś doda przypadek do unii i zapomni go obsłużyć?
Zawęź każdy przypadek i przypisz resztę do `never`. Gdy unia urośnie, reszta przestaje być niczym i przypisanie zawodzi.
Przepis
type Shape = | { kind: 'circle'; r: number } | { kind: 'square'; side: number } function assertNever(value: never): never { throw new Error(`Unhandled: ${JSON.stringify(value)}`) } function area(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.r ** 2 case 'square': return shape.side ** 2 default: return assertNever(shape) } }
Build to kompiluje i sprawdza każdy wynik poniżej.
Jak to działa
- 01
function assertNever(value: never): never {
Parametr o typie `never` nie przyjmuje niczego. Jedyną wartością, jaką da się przekazać, jest ta, którą kompilator już uznał za niemożliwą.
- 02
return assertNever(shape)
W gałęzi domyślnej z każdego znanego przypadku już wrócono, więc `shape` zawęziło się do `never` i to się kompiluje. Dodaj trzeci kształt, a przestanie — w tym wierszu, nazywając typ, o którym zapomniałeś.
- 03
throw new Error
Rzuca zamiast zwracać, bo wartość, która miała być niemożliwa, docierając w czasie wykonania jest błędem, a nie przypadkiem do wchłonięcia.
Co dostajesz
ReturnType<typeof area>→numberShape['kind']→"circle" | "square"Extract<Shape, { kind: 'circle' }>→{ kind: "circle"; r: number; }To dyskryminator umożliwia zawężanie — jedna literalna właściwość, którą ma każdy składnik i której żadne dwa nie dzielą.
Gdzie to zawodzi
Działa tylko wtedy, gdy każdy przypadek zwraca albo rzuca. `break`, który przechodzi do domyślnego, zostawia `shape` niezawężone, a sprawdzenie po cichu przestaje cokolwiek sprawdzać.
Wniosek
To najbardziej opłacalny wzorzec w tym języku. Zamienia „zapomnieliśmy obsłużyć nowy przypadek” z incydentu produkcyjnego w błąd kompilacji.