Menu

Zawężanie typów w TypeScript: jak działa control flow

Zawężanie to sposób, w jaki TypeScript zamienia szeroki typ, taki jak string | number, w konkretny wewnątrz if, switch albo po wczesnym return. Wszystkie formy zawężania w jednym miejscu: typeof, truthiness, równość, in, instanceof, przypisania, predykaty typów i discriminated unions.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Zawężanie typów to ustalanie przez TypeScript bardziej konkretnego typu wartości w danym miejscu kodu na podstawie sprawdzeń, które kod już wykonał. Parametr string | number staje się string wewnątrz if (typeof x === "string") i number w else.

Wywołanie value.toFixed(2) przed sprawdzeniem byłoby błędem kompilacji, bo toFixed nie istnieje w string. Sprawdzenie to zwykły JavaScript wykonywany w czasie działania programu, a zawężanie to kompilator, który je odczytuje i dopasowuje typ. Nic dodatkowego nie jest emitowane.

Analiza przepływu sterowania

TypeScript śledzi każdą ścieżkę przez funkcję: if/else, wczesne return i throw, switch, pętle oraz operatory skrócone &&, ||, ?? i ?:. W każdym miejscu typ zmiennej to to, co nadal jest tam możliwe.

Styl z wczesnym return ("guard clauses") to najczytelniejszy sposób zawężania: najpierw obsłuż nietypowe przypadki, a reszta funkcji pracuje na czystym typie.

Wszystkie sposoby zawężania

FormaPrzykładZawęża
typeoftypeof x === "string"typy proste i funkcje
Truthinessif (x)usuwa null, undefined i literały falsy
Równośćx === "a", x == null, x !== undefinedliterały, null, undefined
in"swim" in petunie obiektów, według właściwości
instanceoferr instanceof TypeErrorinstancje klas
Array.isArrayArray.isArray(x)tablice kontra cała reszta
Przypisaniex = 5do przypisanego typu
Predykat typufunction isUser(x: unknown): x is Userwszystko, co da się sprawdzić
Funkcja asercjifunction assertUser(x: unknown): asserts x is Userwszystko po wywołaniu
Właściwość dyskryminującaswitch (shape.kind)unie ze znacznikiem

Ostatnie trzy omawiają strony o type guards i discriminated unions. Pozostałe są opisane niżej.

Zawężanie przez truthiness

if (x) usuwa null i undefined (oraz typy literałów false, 0, ""). Jest krótkie i ma jedną klasyczną pułapkę: 0 i "" są falsy, więc poprawne wartości są traktowane jak brakujące.

Liczby i stringi porównuj jawnie z undefined albo null (albo użyj ??). Truthiness sprawdza się dla obiektów, tablic i funkcji, które nigdy nie są falsy.

Zawężanie przez równość

===, !==, == i != zawężają obie strony. Porównanie z literałem zawęża do tego literału. == null (luźna równość) dopasowuje w jednym sprawdzeniu zarówno null, jak i undefined i to jedyne miejsce, gdzie luźna równość jest idiomatyczna.

Porównanie dwóch zmiennych zawęża obie do tego, co mogą mieć wspólnego: jeśli a: string | number i b: string | boolean przejdą a === b, obie mają w if typ string.

Operator in

"key" in obj zawęża unię typów obiektów do tych elementów, które mają (albo mogą mieć) tę właściwość.

in działa też na unknown, gdy wiesz już, że to obiekt: po typeof v === "object" && v !== null && "id" in v TypeScript wie, że v ma właściwość id typu unknown. W uniach, które projektujesz sam, wspólna właściwość-znacznik (kind: "fish") jest czytelniejsza niż sprawdzanie metod: ten wzorzec to discriminated union.

Zawężanie przez przypisanie

Zmienna ma typ zadeklarowany i typ zawężony, który podąża za jej przypisaniami. Przypisanie wartości zawęża zmienną do typu tej wartości, w granicach typu zadeklarowanego.

Gdzie zawężanie ginie

Zawężanie jest lokalne i ostrożne. Kilka sytuacji je resetuje:

  • Inne wyrażenie. Sprawdzenie obj.name zawęża obj.name (i obj["name"]), ale nie obj[key], gdy key to zmienna typu string, a nie literał, i nie kopię zrobioną przed sprawdzeniem.
  • Callbacki i ponowne przypisanie. W callbacku zawężona zmienna let zachowuje zawężenie tylko wtedy, gdy po utworzeniu callbacka nie dostaje nowej wartości. const albo parametr, któremu nigdy nie przypisuje się nowej wartości, pozostaje zawężony.
  • Sprawdzenia ukryte w funkcjach pomocniczych. Funkcja isString(x: unknown): boolean nic nie mówi kompilatorowi. Nadaj jej typ zwracany w postaci predykatu typu, x is string, a jej wywołania będą zawężać tak jak typeof.

Kompilator zgłasza index.ts(5,38): error TS18048: 'x' is possibly 'undefined'. Callback mógłby się wykonać później, po x = undefined. Usuń to ostatnie przypisanie (albo skopiuj wartość do const wewnątrz if), a kod się skompiluje i dwa razy wypisze 5.

Funkcję pomocniczą, która zwraca boolean, można naprawić, deklarując, czego dowodzi. Tym właśnie jest type guard:

function isString(value: unknown): value is string {
  return typeof value === "string";
}

Od TypeScript 5.5 kompilator wnioskuje takie predykaty dla prostych funkcji strzałkowych, dlatego list.filter((x) => x !== undefined) zwraca teraz tablicę bez undefined.

Najczęściej zadawane pytania

Czym jest zawężanie typów w TypeScript?

Zawężanie to doprecyzowanie przez TypeScript typu zmiennej w bloku na podstawie sprawdzenia, które wykonuje kod. Po if (typeof x === "string") wartość string | number jest w if tylko string, a w else tylko number. Kompilator śledzi if, else, return, switch, &&, || i ?:, żeby ustalić typ w każdym miejscu, i nazywa się to analizą przepływu sterowania (control flow analysis).

Dlaczego TypeScript nie zawęża mojego typu?

Częste przyczyny: sprawdzenie dotyczy innego wyrażenia niż to, którego używasz (sprawdzane obj.a, używane obj[key] z key typu string); wartość to let, której przypisano nową wartość po utworzeniu callbacka, więc callback traci zawężenie; albo sprawdzenie jest ukryte w funkcji pomocniczej, która zwraca zwykły boolean zamiast predykatu typu x is T.

Czy zawężanie typów działa w czasie wykonania?

Sprawdzenia tak: typeof, instanceof, in i === to zwykły JavaScript, który się wykonuje. Samo zawężanie działa tylko w czasie kompilacji. TypeScript odczytuje twoje sprawdzenia i dopasowuje do nich typy statyczne, a do wyemitowanego JavaScriptu nic nie jest dodawane.

Jak zawęzić typ unknown w TypeScript?

Tymi samymi sprawdzeniami: typeof value === "string", Array.isArray(value), value instanceof Date, a dla obiektów typeof value === "object" && value !== null && "id" in value. Do sprawdzeń wielokrotnego użytku napisz funkcję type guard z typem zwracanym value is T.

Jak odfiltrować undefined z tablicy w TypeScript?

Od TypeScript 5.5 items.filter((x) => x !== undefined) zwraca T[] bez undefined, bo kompilator wnioskuje callback jako predykat typu. W starszych wersjach napisz predykat sam: items.filter((x): x is T => x !== undefined).

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ