Zagadnienie 6 z 12
Typy warunkowe
Typ warunkowy to `if` na poziomie typów. Zadaje zawsze to samo pytanie: czy lewa strona jest przypisywalna do prawej? Nie „czy są równe”, nie „czy to instancja” — przypisywalna, i to w jedną stronę.
Przypisywalność, nie równość
type A = 'hello' extends string ? true : false // true type B = string extends 'hello' ? true : false // false type C = never extends string ? true : false // true — never fits anywhere type D = { a: 1; b: 2 } extends { a: 1 } ? true : false // true
Typ węższy jest przypisywalny do szerszego, więc test ma kierunek. Obiekt z dodatkowymi właściwościami jest przypisywalny do obiektu z mniejszą liczbą — dlatego `extends` nie sprawdzi, czy dwa typy obiektowe mają ten sam kształt.
Gdy naprawdę potrzebujesz „tego samego typu”, potrzebujesz pomocnika `Equal`, na którym oparty jest każdy test na tej stronie — dwa odroczone warunki porównywane co do tożsamości, co jest surowsze niż przypisywalność w obie strony.
Gałęzie sprawdzane są po kolei
type Describe<T> = T extends (...args: never[]) => unknown ? 'function' : T extends unknown[] ? 'array' : T extends object ? 'object' : 'primitive'
Węższe przypadki muszą iść pierwsze. Funkcje i tablice są obiektami, więc wczesne sprawdzenie `extends object` je pochłonie — to klasyczna przyczyna `DeepReadonly`, które po cichu niszczy każdą napotkaną funkcję.
Najczęstsza błędna odpowiedź
// Intent: is T exactly string? type IsString<T> = T extends string ? true : false IsString<'hello'> // true — but 'hello' is not string, it is narrower IsString<any> // boolean — any matches both branches at once
`extends` odpowiada na „mieści się w”, więc każdy literał łańcuchowy przechodzi test na `string`. A `any` jest przypisywalne do wszystkiego, więc warunek z `any` zwraca unię obu gałęzi — wynik, o który autor prawie nigdy nie prosił.
Do zapamiętania
Warunki pytają o przypisywalność i sprawdzają gałęzie po kolei. Najpierw węższe, a po `Equal` sięgaj, gdy tylko potrzebujesz prawdziwej tożsamości.