Typ literałowy to typ z dokładnie jedną wartością: "up" to typ, którego jedynym elementem jest string "up", a 404 to typ, którego jedynym elementem jest liczba 404. Same w sobie nie są zbyt przydatne. Połączone w unię dają zmienną, która przyjmuje stały zestaw wartości i nic poza nim.
Bez komentarza @ts-expect-error ostatnie wywołanie to błąd kompilacji (TS2345). Z komentarzem program się kompiluje, a wywołanie i tak się wykonuje i wypisuje moving north by 1: typy literałowe istnieją tylko dla kompilatora, a w czasie działania wartość to zwykły string.
Literały string, number i boolean
Każdą wartość typu string, number, bigint albo boolean można zapisać jako typ. Kompilator przyjmuje wtedy tylko tę dokładną wartość.
let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;
type Port = 80 | 443 | 8080;
const port: Port = 443;
Sam boolean to po prostu unia true | false, dlatego zawężenie boolean przez if (flag) zostawia false w gałęzi else.
| Typ literałowy | Dopuszcza | Szerszy typ |
|---|---|---|
"GET" | tylko string "GET" | string |
404 | tylko liczbę 404 | number |
10n | tylko bigint 10 | bigint |
true | tylko true | boolean |
Unie literałów
Typowe zastosowanie to unia, która wymienia każdą dozwoloną wartość. Wewnątrz funkcji kompilator zawęża unię w miarę sprawdzania, więc każda gałąź dokładnie wie, jaką ma wartość.
Unia literałów stringowych to typowa w TypeScript alternatywa dla enuma. Nic nie kosztuje w czasie działania, wartości to zwykłe stringi, które można logować i wysyłać w JSON-ie, a literówka jest błędem kompilacji. Więcej o zaletach i wadach na stronie o enumach.
Poszerzanie: let kontra const
Gdy TypeScript wnioskuje typ z literału, patrzy, czy wartość może się zmienić. Zmiennej const nigdy nie da się przypisać na nowo, więc zachowuje typ literałowy. Zmienną let da się, więc jej typ zostaje poszerzony do typu ogólnego.
Najedź kursorem na każdą nazwę w edytorze, żeby zobaczyć wywnioskowany typ. Jeśli chcesz, żeby let przechowywało tylko określone wartości, oznacz jego typ: let mode: "light" | "dark" = "light".
Dlaczego właściwości obiektów się poszerzają
Właściwości literału obiektowego są modyfikowalne, więc też się poszerzają, nawet gdy obiekt jest zapisany w const. To najczęstszy sposób, w jaki trafia się na typy literałowe przypadkiem:
Kompilator zgłasza:
index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
req ma wywnioskowany typ { url: string; method: string }, bo późniejszy kod mógłby wykonać req.method = "DELETE". Są trzy rozwiązania:
Czwarta opcja to satisfies, które sprawdza obiekt względem typu, a jednocześnie zachowuje literałowe typy jego właściwości.
as const
as const to const assertion. Umieść je po wyrażeniu, a kompilator wywnioskuje najwęższy możliwy typ:
- wartości string, number i boolean zachowują typy literałowe
- właściwości obiektów stają się
readonly - literały tablic stają się krotkami
readonlyo stałej długości
Asercja działa tylko w czasie kompilacji. Wygenerowany JavaScript to ten sam literał obiektowy bez as const, więc nic nie powstrzyma innego kodu przed modyfikacją go w czasie działania. Jeśli potrzebujesz gwarancji w czasie działania, wywołaj też Object.freeze.
Typ unii z tablicy as const
Częsty wzorzec to trzymanie dozwolonych wartości w jednej tablicy, po której można iterować w czasie działania, i wyprowadzanie z niej typu unii. (typeof arr)[number] oznacza „typ dowolnego elementu arr”.
Bez as const ROLES miałoby typ string[], a Role byłoby zwykłym string. Rzutowanie na readonly string[] w isRole jest potrzebne, bo includes na krotce literałów przyjmuje tylko te literały, a funkcja ma właśnie sprawdzić string, który może nie być żadnym z nich. Ten sam wzorzec z obiektem działa dla map klucz/wartość: const Status = { Active: "active", Banned: "banned" } as const, a potem type Status = (typeof Status)[keyof typeof Status].
Parametry typu const
Funkcja generyczna zwykle poszerza przekazane jej literały. Od TypeScript 5.0 można oznaczyć parametr typu jako const, dzięki czemu kompilator wnioskuje argument tak, jakby miał as const, bez proszenia wywołującego o dopisanie tego.
To głównie narzędzie dla autorów bibliotek: definicje tras, buildery i funkcje pomocnicze do schematów korzystają z niego, żeby wywołujący dostawali dokładne typy ze zwykłych literałów.
Znaczenia const
Słowo kluczowe const pojawia się w kodzie TypeScript w czterech różnych miejscach:
| Składnia | Rodzaj | Co robi |
|---|---|---|
const x = 1 | deklaracja JavaScriptu | zmiennej nie można przypisać na nowo; wartość literałowa zachowuje typ literałowy |
expr as const | asercja TypeScript | najwęższy typ: literały, właściwości readonly, krotki readonly |
function f<const T>() | parametr typu TypeScript | wnioskuje argumenty tak, jakby miały as const |
const enum E {} | enum TypeScript | enum, którego składowe są wstawiane w miejsce użycia podczas kompilacji |
Żadne z nich nie zamraża obiektu w czasie działania. const obj = { a: 1 } nadal pozwala na obj.a = 2; błędem jest tylko ponowne przypisanie samego obj.
Najczęściej zadawane pytania
Czym jest typ literałowy w TypeScript?
To typ, który dopuszcza dokładnie jedną wartość. "GET" to typ, którego jedyną wartością jest string "GET", 404 to typ, którego jedyną wartością jest liczba 404, a true to typ, którego jedyną wartością jest true. Najbardziej przydają się połączone w unie, takie jak type Method = "GET" | "POST".
Co robi as const w TypeScript?
as const to const assertion. Każe kompilatorowi wywnioskować najwęższy typ wyrażenia: wartości string i number zachowują typy literałowe, właściwości obiektów stają się readonly, a literały tablic stają się krotkami readonly. Zmienia tylko typ; wartość w czasie działania to ten sam zwykły obiekt albo tablica i nie jest zamrożona.
Dlaczego TypeScript wnioskuje string zamiast mojego literału?
Bo wartość jest modyfikowalna. let x = "a" i właściwość w { method: "GET" } można później przypisać na nowo, więc TypeScript poszerza je do string. Zmienna const zachowuje typ literałowy "a". Żeby zachować literały w obiekcie, oznacz go typem literałowym, użyj as const albo satisfies.
Czym różni się const od as const?
const to deklaracja JavaScriptu: zmiennej nie można przypisać na nowo, ale obiekt, na który wskazuje, nadal można zmieniać. as const to asercja typu TypeScript: sprawia, że każda właściwość wartości jest w systemie typów readonly i literałowa. Żadne z nich nie zamraża obiektu w czasie działania; do tego służy Object.freeze.
Jak uzyskać typ unii z tablicy stringów?
Zadeklaruj tablicę z as const, a potem zaindeksuj jej typ przez number: const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; daje "admin" | "user". Bez as const tablica ma typ string[], a wynikiem jest zwykły string.