Typ unii wymienia warianty rozdzielone przez |: wartość typu string | number to string albo liczba. Unie to sposób, w jaki TypeScript opisuje wartości, które mogą legalnie przyjmować więcej niż jedną postać, a kompilator każe ci sprawdzić, z którą postacią masz do czynienia, zanim użyjesz czegoś, co dotyczy tylko jej.
W każdej gałęzi sprawdzenia typeof zmienna id ma jeden typ. Ten krok to zawężanie i to on sprawia, że unie są praktyczne.
Dozwolone są tylko wspólne składowe
Przed zawężeniem możesz używać tylko tego, co obsługuje każdy element unii. toString() istnieje zarówno w stringach, jak i w liczbach, więc jest w porządku, a toUpperCase() istnieje tylko w stringach:
index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
Druga linia nazywa element, któremu brakuje tej właściwości. Ta sama reguła działa w drugą stronę: wartości string | number nie da się przekazać do parametru typu string (TS2345), bo może to być liczba. Unia przyjmuje więcej wartości, a w zamian pozwala zrobić z nimi mniej, dopóki ich nie sprawdzisz.
Zawężanie unii
Zawężanie używa zwykłych sprawdzeń z JavaScriptu. TypeScript śledzi przepływ sterowania i usuwa elementy, gdy zostają wykluczone, więc po ostatnim sprawdzeniu zostaje tylko jeden:
| Sprawdzenie | Zawęża | Dobre dla |
|---|---|---|
typeof x === "string" | do typu prostego | string, number, boolean, bigint, symbol, undefined, function |
x === null, x === "a" | do porównywanej wartości | null, undefined, elementy będące literałami |
Array.isArray(x) | do elementu tablicowego | tablice |
x instanceof Date | do klasy | instancje klas |
"meow" in x | do elementów, które mają tę właściwość | typy obiektów |
x.kind === "circle" | do elementu z tym znacznikiem | discriminated unions |
isCat(x) (zwraca x is Cat) | do tego, co mówi funkcja | cokolwiek, własna logika |
Pełną listę form zawężania znajdziesz na stronie o zawężaniu typów.
Unie typów literałów
Unia wartości literałów to zamknięty zbiór dozwolonych wartości. To najczęstsza unia w prawdziwym kodzie:
type Status = "idle" | "loading" | "success" | "error";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = "on" | "off" | boolean; // boolean is itself true | false
let current: Status = "idle";
current = "loading"; // fine
current = "finished"; // error TS2322: Type '"finished"' is not assignable to type 'Status'.
Porównanie z literałem zawęża: po if (current === "error") gałąź else wie, że current to jedna z pozostałych trzech wartości. W wielu projektach unie literałów zastępują enumy. O as const i o tym, jak wyprowadzić taką unię z tablicy, przeczytasz na stronie o typach literałów.
Unie typów obiektów
Gdy elementy są typami obiektów, właściwości wspólne dla wszystkich są dostępne od razu. Dla reszty sprawdź przez in, czy właściwość istnieje:
Przy uniach kilku kształtów obiektów czytelniejszy jest wzorzec ze wspólną właściwością-literałem, taką jak kind: "cat" / kind: "fish". Sprawdzenie tej jednej właściwości zawęża cały obiekt, a switch po niej można sprawdzić pod kątem pełnego pokrycia. Ten wzorzec to discriminated union.
Tablice i unie
To, gdzie stoją nawiasy, całkowicie zmienia znaczenie:
| Typ | Oznacza | Przykładowa wartość |
|---|---|---|
(string | number)[] | tablicę, której każdy element to string albo liczba | [1, "two", 3] |
string[] | number[] | tablicę samych stringów albo tablicę samych liczb | ["a", "b"] |
string | number[] | string albo tablicę liczb (| wiąże słabiej niż []) | "text" |
Przy iterowaniu po (string | number)[] każdy element ma typ unii i wymaga zawężenia, jak w callbacku reduce powyżej. Metody takie jak map i filter działają też na string[] | number[], a callback dostaje string | number.
Unie z null i undefined
Najczęstsza unia ze wszystkich to "wartość albo nic": string | null, User | undefined. To właśnie zwracają Array.prototype.find i Map.prototype.get, a właściwość opcjonalna name?: string przy odczycie ma typ string | undefined. Obsługa takich wartości przez ?., ?? i sprawdzanie null ma własną stronę: null i undefined.
Żeby usunąć elementy z istniejącej unii na poziomie typów, użyj wbudowanych typów narzędziowych: Exclude<"a" | "b" | "c", "a"> to "b" | "c", a NonNullable<string | null> to string.
Najczęściej zadawane pytania
Czym jest typ unii w TypeScript?
To typ złożony z kilku wariantów połączonych przez |. Wartość typu string | number może być stringiem albo liczbą. Kompilator pozwala używać tylko tego, co wszystkie elementy mają wspólnego, dopóki nie zawęzisz wartości do jednego elementu sprawdzeniem takim jak typeof value === "string".
Dlaczego TypeScript mówi, że właściwość nie istnieje w typie unii?
Bo co najmniej jeden element unii jej nie ma. Błąd TS2339, na przykład Property 'toUpperCase' does not exist on type 'string | number', oznacza, że wartość może być liczbą, a liczba nie ma toUpperCase. Najpierw zawęź typ (typeof, in, Array.isArray, instanceof albo sprawdzenie znacznika), a potem użyj właściwości konkretnego elementu.
Jak zadeklarować tablicę, która przechowuje więcej niż jeden typ?
Umieść unię w nawiasach: (string | number)[] albo Array<string | number>, gdzie każdy element może mieć którykolwiek z typów. string[] | number[] to coś innego: cała tablica to same stringi albo same liczby. Bez nawiasów string | number[] oznacza string albo tablicę liczb.
Czym różni się unia od przecięcia typów?
Unia A | B to wartość, która ma jeden z typów, więc możesz używać tylko tego, co wspólne. Przecięcie A & B to wartość, która jest oboma naraz, więc ma wszystkie składowe obu. Dla typów obiektów A | B przyjmuje więcej wartości, a A & B wymaga więcej właściwości.
Jak sprawdzić, jakiego typu jest wartość unii?
Użyj sprawdzenia w czasie wykonania, które TypeScript rozumie: typeof x === "string" dla typów prostych, Array.isArray(x) dla tablic, x instanceof Date dla klas, "prop" in x dla kształtów obiektów albo x.kind === "circle", gdy elementy mają wspólny znacznik z literałem. Do własnej logiki napisz funkcję type guard zwracającą x is T.