value satisfies Type sprawdza podczas kompilacji, czy value pasuje do Type, a potem zostawia własny, bardziej precyzyjny typ wartości w spokoju. Adnotacja zastąpiłaby ten precyzyjny typ przez Type; satisfies weryfikuje bez poszerzania.
satisfies nadal sprawdza: brakujący kolor, błędnie wpisany klucz, taki jak bleu, albo wartość w rodzaju true to błąd kompilacji w tej linii. Istnieje od TypeScript 4.9 i, jak każda adnotacja typu, jest usuwany z wynikowego JavaScriptu.
Problem, który rozwiązuje satisfies
Z adnotacją typu typ zmiennej jest adnotacją. Kompilator zapomina, co widział w literale. Tu ta sama paleta ma zamiast tego adnotację i TypeScript już nie wie, że green to string:
Kompilator zgłasza:
index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
Property 'toUpperCase' does not exist on type '[number, number, number]'.
Przed TypeScript 4.9 do wyboru było: dodać adnotację i wszędzie zawężać ręcznie (typeof palette.green === "string") albo pominąć adnotację i stracić sprawdzenie. satisfies daje jedno i drugie. Zamień : Record<ColorName, Color> na satisfies Record<ColorName, Color> po zamykającym nawiasie klamrowym, a kod się uruchomi.
satisfies a adnotacja typu a as
Ten sam obiekt ustawień zapisany na trzy sposoby:
as przepuścił brakujące lang, a asserted.lang w czasie działania to undefined, choć jego typ mówi string. Usuń lang z dwóch pozostałych linii, a obie zakończą się błędem TS2741, Property 'lang' is missing in type ....
Adnotacja const x: T = v | Asercja v as T | v satisfies T | |
|---|---|---|---|
| Brakujące właściwości | błąd | dozwolone | błąd |
| Nadmiarowe właściwości (literał obiektu) | błąd | dozwolone | błąd |
| Zły typ właściwości | błąd | tylko gdy typy się nie pokrywają | błąd |
Typ x potem | T | T | wywnioskowany typ v |
Typy literałowe ("dark", 8080) | poszerzone do T | poszerzone do T | zachowane, gdzie T na nie pozwala |
Klucze Record<string, ...> | dowolny string (literówki się kompilują) | dowolny string | dokładnie zapisane klucze |
| Efekt w czasie działania | brak | brak | brak |
Praktyczna zasada: dodawaj adnotację, gdy chcesz, żeby zmienna miała zadeklarowany typ (wartość, którą będziesz ponownie przypisywać, publiczne API), a satisfies używaj, gdy chcesz sprawdzenia, ale własny typ wartości jest bardziej przydatny.
Wyłapywanie błędów w literałach obiektów
satisfies wykonuje pełne sprawdzenie przypisywalności, łącznie ze sprawdzaniem nadmiarowych właściwości, więc literówki w kluczach to błędy:
type Route = { path: string; method: "GET" | "POST" };
const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?
Sprawdzenie nadaje też literałowi typ kontekstowy, tak jak adnotacja. Ma to znaczenie na dwa sposoby. Literały tekstowe zostają typami literałowymi, gdy typ docelowy ich oczekuje: { path: "/", method: "GET" } satisfies Route ma method: "GET", podczas gdy ten sam obiekt bez adnotacji dałby method: string. A parametry callbacków są wnioskowane z typu docelowego:
Klucze Record pozostają znane
Częste zastosowanie to tablica wyszukiwania. Z adnotacją Record<string, T> każdy string jest poprawnym kluczem, a literówka się kompiluje i w czasie działania zwraca undefined. Z satisfies wartości nadal są sprawdzane względem T, ale typ zmiennej wymienia dokładnie te klucze, które zapisano:
keyof typeof endpoints jest przydatne tylko dlatego, że klucze przetrwały. Z adnotacją byłoby zwykłym string.
Aby wymagać stałego zbioru kluczy, użyj satisfies z Record po unii: satisfies Record<"dev" | "prod", string> zgłasza brakujące prod błędem TS2741, a nieznane staging błędem TS2353.
as const satisfies
as const i satisfies można łączyć. Pisz najpierw as const: czyni wartość głęboko readonly z typami literałowymi, a potem satisfies sprawdza tę dokładną wartość.
Każda trasa jest sprawdzana względem Route (method: "PUT" byłoby błędem), a krotka typów literałowych pozostaje dostępna, więc Path to unia prawdziwych ścieżek. Jako typu docelowego użyj readonly Route[] (albo ReadonlyArray<Route>), bo tablica as const jest readonly.
Obiekty konfiguracji
To w konfiguracji satisfies naprawdę się opłaca: kształt musi się zgadzać, a kod w innych miejscach potrzebuje precyzyjnych wartości.
Pomiń wpis production, pomyl pisownię logLevel albo napisz logLevel: "verbose", a kompilator wskaże dokładną linię. Ten sam wzorzec pasuje do plików *.config.ts: export default { ... } satisfies SomeConfig sprawdza cały plik, a eksportowany obiekt zachowuje swoje wartości literałowe.
Kiedy nie używać satisfies
- Zmienna będzie ponownie przypisywana.
let cfg = { port: 3000 } satisfies { port: number | string }nadajecfgtyp{ port: number }, więc późniejszecfg = { port: "80" }się nie uda (TS2322). Dodawaj adnotacje do zmiennych, które zamierzasz zmieniać. - Celowo chcesz zadeklarowanego typu. Dla wartości zwracanej funkcji albo eksportowanej stałej, która jest częścią API, typ z adnotacji jest kontraktem, a ujawnienie dokładnego typu literałowego może sprawić, że późniejsze zmiany staną się breaking changes.
- Wartość nie jest literałem.
satisfiesbłyszczy na literałach obiektów i tablic. Na zmiennej lub wyniku wywołania to zwykłe sprawdzenie przypisywalności, które daje już adnotacja.
Najczęściej zadawane pytania
Co robi satisfies w TypeScript?
expression satisfies Type sprawdza podczas kompilacji, czy wyrażenie jest przypisywalne do Type, zgłaszając brakujące właściwości, nadmiarowe właściwości i złe typy wartości, a potem zostawia własny wywnioskowany typ wyrażenia bez zmian. Dostajesz bezpieczeństwo adnotacji i precyzję wnioskowania. Jest usuwany z wynikowego JavaScriptu.
Czym różni się satisfies od adnotacji typu?
Oba sprawdzają wartość. Adnotacja (const x: T = ...) nadaje potem zmiennej typ T, zapominając, co kompilator wiedział o wartości (typy literałowe, który element unii ma każda właściwość, jakie klucze istnieją). satisfies T zachowuje wywnioskowany typ, więc wiadomo, że x.someKey istnieje, a właściwość string | number przechowująca string ma typ string.
Czym różni się satisfies od as w TypeScript?
as to asercja: nadpisuje typ i prawie niczego nie sprawdza, więc brakujące właściwości przechodzą niezauważone. satisfies to sprawdzenie: wartość musi naprawdę pasować do typu, a jej własny wywnioskowany typ zostaje zachowany. Gdy oba by się skompilowały, satisfies jest bezpieczniejszym wyborem.
Co oznacza as const satisfies?
Stosuje oba: as const czyni wartość głęboko readonly z typami literałowymi, a potem satisfies sprawdza ten wynik względem typu. Pisz najpierw as const: const routes = [...] as const satisfies readonly Route[];. Zmienna zachowuje dokładne typy literałowe do późniejszego użycia, a zły wpis nadal jest błędem kompilacji.
W której wersji TypeScript dodano satisfies?
W TypeScript 4.9, wydanym w listopadzie 2022. To zwykła składnia, którą da się usunąć, więc działa też z wbudowanym type strippingiem w Node, a obsługuje ją każda aktualna wersja TypeScriptu (łącznie z 7).