type Colors = 'primary' | 'secondary' | 'danger';
type Palette = Record<Colors, string | { hex: string }>;
// 1. Anotare explicită: pierde tipul literal exact
const themeExplicit: Palette = {
primary: '#0070f3',
secondary: { hex: '#ff0000' },
danger: '#ff4444'
};
// themeExplicit.primary.toUpperCase(); // Error: Property 'toUpperCase' does not exist on string | { hex: string }
// 2. Operatorul 'satisfies': validează structura ȘI păstrează tipul exact
const themeSatisfies = {
primary: '#0070f3',
secondary: { hex: '#ff0000' },
danger: '#ff4444'
} satisfies Palette;
themeSatisfies.primary.toUpperCase(); // OK! TS știe că e string exact.
themeSatisfies.secondary.hex; // OK!Salutare tuturor. Văd constant în PR-uri un abuz masiv de Type Assertions (as) sau, de când a apărut TS 4.9, oameni care trântesc satisfies peste tot fără să înțeleagă ce face sub capotă. Anul trecut, când am refăcut o aplicație de i18n pe un codebase cu vreo 40k linii de cod, ne-am lovit grav de problema asta și am stabilit niște reguli clare.
Hai să trecem prin cele trei opțiuni și să vedem un ghid decizional simplu.
1. Anotarea explicită (const x: Type)
Este abordarea clasică. Îi spui compilerului: „Această variabilă trebuie să respecte exact această interfață”.
Avantaj: Dacă uiți un câmp obligatoriu, primești eroare fix unde declari variabila, nu unde o folosești.
Trade-off: Pierzi tipizarea mai îngustă (narrowing). Dacă tipul conține o uniune precum string | RGB, TypeScript va considera că valoarea e uniunea, chiar dacă tu ai trecut un string clar. N-o să poți apela metode specifice de string fără Type Guards ulterior.
2. Assertion-ul as (Type Casting)
Operatorul as îi spune TypeScript-ului: „Taci din gură, știu eu mai bine decât tine”.
Avantaj: Util când treci dinspre un domeniu netipizat (de exemplu, raspuns dintr-un API extern sau un JSON.parse) spre codul tău tipizat.
Trade-off: E cel mai periculos mecanism. Ocolește complet verificările compilerului. Dacă schimbi o proprietate în interfață, as nu-ți dă nicio eroare la build, ci iți crăpă aplicația senin la runtime în producție. Am scos peste 50 de as-uri la un refactoring masiv și am prins 4 bug-uri ascunse.
3. Operatorul satisfies
A apărut în TS 4.9 și rezolvă fix dilema dintre primele două: validează că un obiect se potrivește cu un tip, dar fără să-i suprascrie tipul inferat.
Avantaj: Obții autocomplete instant pe chei, ești protejat dacă scrii greșit un nume de proprietate, dar păstrezi literals și tipurile specifice ale valorilor.
Trade-off: Nu ajută la optimizarea tipurilor de returnate în funcții complexe și nu funcționează ca un Type Guard la runtime.
Ghid decizional: Când ce folosești?
- Cazul 1: Config-uri, Dictionaries, Design Systems. Folosește
satisfies. Vrei să te asiguri că ai toate culorile/rutele definite, dar vrei ca TS să știe exact ce string literal e pe fiecare cheie ca să ai autocomplete impecabil. - Cazul 2: Boundaries de date (API, LocalStorage, WebSockets). Nu folosi
asorbește! Folosește o librărie de validare (Zod/Valibot) și inferează tipul. Foloseșteasdoar dacă ești 100% sigur pe un mock în teste sau pe un element din DOM (document.getElementById as HTMLInputElement). - Cazul 3: Stări mutate ulterior sau obiecte simple. Folosește anotarea explicită
const u: User = .... În cazul ăsta vrei ca obiectul să fie strict blocat la tipul generalUserpentru modificările ulterioare. - Cazul 4: Autocomplete pe proprietăți opționale. Folosește
satisfies. Îți prinde typo-urile imediat fără să-ți transforme proprietățile definite înundefinedla analiză statică.
Concluzia mea e simplă: folosiți anotarea explicită de bază pentru 80% din cod, înlocuiți aproape orice as cu satisfies când lucrați cu obiecte de configurare și lăsați as doar pentru cazuri extreme la marginea aplicației.
Voi ce reguli aveți în linter pentru as? L-ați interzis complet prin ESLint sau îl mai lăsați liber?