type Colors = "primary" | "secondary";
type Theme = Record<Colors, string | { r: number, g: number, b: number }>;
// ❌ Adnotare explicită - pierdem precizia
const theme1: Theme = {
primary: "#ff0000",
secondary: { r: 0, g: 255, b: 0 }
};
// theme1.primary.toUpperCase(); // Eroare: Property 'toUpperCase' does not exist on type 'string | { r: ... }'
// ✅ satisfies - validăm DAR păstrăm precizia
const theme2 = {
primary: "#ff0000",
secondary: { r: 0, g: 255, b: 0 }
} satisfies Theme;
console.log(theme2.primary.toUpperCase()); // Merge! TS știe că e string.Să fim sinceri: mulți dintre noi am învățat TypeScript punând : peste tot. Credeam că dacă scriu const user: User = ..., sunt în siguranță. Ei bine, după vreo 10 ani de spart ecrane, mi-am dat seama că adnotările explicite sunt, de multe ori, o piedică în calea inferenței inteligente. Recent, la un proiect cu peste 200 de rute de API și vreo 15k linii de cod, am făcut trecerea masivă către operatorul satisfies și am eliminat vreo 30% din boilerplate-ul inutil.
Hai să vedem care-i treaba cu cele trei metode și unde se rupe filmul pentru fiecare.
1. Adnotarea explicită (The Classic Way)
Când scrii const config: Config = { ... }, îi spui compilatorului: „Vreau ca acest obiect să fie EXACT de tipul Config”. Problema e că TypeScript va face „type widening”. Dacă ai un câmp opțional sau un union, TS va uita valorile specifice pe care le-ai introdus și va reține doar tipul generic.
Am pățit-o la un sistem de teme (Light/Dark). Aveam un obiect unde cheile erau culorile. Folosind adnotarea explicită, când încercam să accesez theme.primary, TS îmi dădea eroare pentru că el credea că primary s-ar putea să nu existe, deși eu tocmai îl definisem. E frustrant să lupți cu compilatorul când tu știi clar ce ai în cod.
2. Operatorul satisfies (The Sweet Spot)
Aici se schimbă jocul. Introdus în TS 4.9, satisfies verifică dacă un obiect respectă un contract, dar păstrează cel mai specific tip posibil (inferred type).
La proiectul menționat, aveam un map de configurații pentru diferite regiuni. Folosind satisfies, am putut să validez că toate regiunile au câmpurile obligatorii, dar am păstrat și accesul la proprietățile specifice fiecărei regiuni fără să fac type casting manual. Practic, câștigi validare fără să pierzi precizia. Merge brici pentru obiecte de configurare, rute sau setări de UI.
3. Type Assertions (Operatorul as)
Ăsta e butonul de „taci, că știu eu mai bine”. L-am folosit și eu excesiv la început, dar e periculos. as nu verifică nimic; el doar forțează compilatorul să te creadă pe cuvânt.
Am avut un bug nenorocit unde un coleg a pus as User pe un obiect care venea dintr-un API extern. API-ul s-a schimbat, câmpul id a devenit uuid, dar codul a continuat să compileze fericit. Runtime-ul a bubuit, evident. Sfatul meu de senior: folosește as doar când interfațezi cu librării externe scrise prost în JS sau când faci unit teste și ai nevoie de un mock rapid. În rest, e o bombă cu ceas.
4. Când rămânem la adnotări explicite?
Nu mă înțelege greșit, nu aruncăm adnotările la gunoi. Ele rămân sfinte la definirea argumentelor funcțiilor și la return types. Dacă nu pui tipul de return la o funcție exportată, te bazezi pe inferență care poate deveni lentă la proiecte mari (crește timpul de build cu 10-15%).
Trade-off-ul e simplu: folosește adnotări explicite la „granițe” (funcții, API-uri, exporturi) și satisfies în interiorul logicii, pentru obiecte și configurări.
În concluzie, dacă vrei un cod care să te ajute, nu să te încurce, începe să înlocuiești adnotările pe variabile locale cu satisfies. O să vezi imediat cum autocompletion-ul devine mult mai precis. Voi ce folosiți mai des pentru obiectele de config, adnotarea clasică sau ați trecut deja pe satisfies?