Wróć do listy
11 września 2026•4 min czytania

TypeScript satisfies: wąskie literały bez poszerzania typu

Adnotacja typu poszerza literały obiektu. satisfies sprawdza kształt i zostawia wąską inferencję, więc autocomplete i narrowing dalej działają.

TypeScriptFrontendReact

Dopisujesz adnotację do obiektu konfiguracji „dla bezpieczeństwa” i nagle palette.green.toUpperCase() wywala się w checkerze. W runtime to nadal string. TypeScript po prostu o tym zapomniał.

Klasyczna pułapka: adnotacja zastępuje typ wywnioskowany i poszerza literały. satisfies sprawdza kształt i zostawia wąską inferencję, więc autocomplete i narrowing dalej działają. Oficjalny opis: TypeScript 4.9 — The satisfies operator.

Problem: adnotacja zjada literały

Mapujesz identyfikatory tras na loadery w aplikacji React / Next.js (przykład):

type RouteId = "home" | "blog" | "about";
type Loader = () => Promise<{ title: string }>;

const loaders: Record<RouteId, Loader> = {
  home: async () => ({ title: "Home" }),
  blog: async () => ({ title: "Blog" }),
  about: async () => ({ title: "About" }),
};

// OK... aż zechcesz listę kluczy jako tuple literałów:
const routeIds = Object.keys(loaders);
// string[] - nie ("home" | "blog" | "about")[]

Albo klasyczny przykład z paletą z dokumentacji. Adnotacja Record łapie literówkę bleu, ale palette.green staje się string | RGB i metody stringa znikają.

Rozwiązanie: satisfies zamiast poszerzania

Użyj satisfies, gdy zależy Ci na precyzyjnym typie wartości i jednocześnie na sprawdzeniu kształtu:

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
  blue: [0, 0, 255],
} satisfies Record<Colors, string | RGB>;

// Nadal string - metody działają
const green = palette.green.toUpperCase();

// Nadal tuple - indeks zostaje precyzyjny
const r = palette.red[0];

Co się zmienia:


  1. Brakujące albo źle nazwane klucze wywalają check (jak przy adnotacji Record).

  2. Typy właściwości zostają konkretne ("#00ff00" zostaje stringiem, nie string | RGB).

  3. Masz poręcz bez wyrzucania inferencji.

  4. Dla mapy tras ten sam wzorzec:

type RouteId = "home" | "blog" | "about";
const loaders = {
  home: async () => ({ title: "Home" }),
  blog: async () => ({ title: "Blog" }),
  about: async () => ({ title: "About" }),
} satisfies Record<RouteId, () => Promise<{ title: string }>>;

type KnownRoute = keyof typeof loaders; // "home" | "blog" | "about"

Opcjonalnie, gdy chcesz też readonly literały: as const satisfies SomeType (kolejność ma znaczenie: najpierw as const, potem satisfies). Ten układ często pojawia się przy configach i mapach i18n; warto też zajrzeć do Total TypeScript o satisfies.

Pułapki i ograniczenia

  • Bierz satisfies dla literałów obiektów, które potem czytasz właściwość po właściwości (configi, tabele tras, flagi).
  • Zostaw zwykłą adnotację, gdy chcesz typ poszerzony (np. mutowalny Record, do którego później przypisujesz).
  • satisfies działa tylko w compile-time. Nie ma go w runtime i nie waliduje payloadów z API.
  • Wymaga TypeScript 4.9+. Na starszych projektach najpierw podnieś typescript (i wersję TS w edytorze).
  • Nie zamraża obiektu i nic nie kopiuje. Mutowalność zostaje, dopóki nie dodasz as const / readonly.
  • Excess property checks nadal działają jak dla literałów; zagnieżdżone obiekty czasem potrzebują własnego satisfies.
  • Zbyt luźne ograniczenie po prawej (unknown, szerokie unie) daje mało korzyści. Doprecyzuj kształt, który naprawdę masz na myśli.
  • Gdy mid-level frontend mówi „otypowałem obiekt i straciłem autocomplete”, zwykle winne jest poszerzenie przez adnotację. satisfies zostawia check i wąski typ. Użyj tego przy kolejnej mapie konfiguracji zamiast kolejnej adnotacji „dla bezpieczeństwa”.