type Color = "red" | "green" | "blue" | { custom: string };
// CAZUL 1: Pierdem precizia cu adnotare explicită
const theme1: Record<string, Color> = {
primary: "red",
secondary: { custom: "#ff0000" }
};
// theme1.secondary.custom -> Error: Property 'custom' does not exist on type 'Color'
// CAZUL 2: Siguranță și precizie cu 'satisfies'
const theme2 = {
primary: "red",
secondary: { custom: "#ff0000" }
} satisfies Record<string, Color>;
// theme2.secondary.custom -> Funcționează perfect!
console.log(theme2.secondary.custom);Dilema: De ce avem trei moduri de a face același lucru?
Salutare tuturor. Recent, pe un proiect de refactoring la un dashboard destul de stufos, m-am lovit din nou de veșnica discuție la code review: „De ce ai pus adnotare aici și nu ai lăsat inferența?”. În TypeScript, avem tendința să fim ori prea stricți, ori prea relaxați.
Mulți developeri vin din Java sau C# și simt nevoia să scrie const x: T = ... peste tot. Alții, mai grăbiți, aruncă un as T doar ca să tacă compilatorul. Dar de la versiunea 4.9, avem operatorul satisfies, care pentru mine a schimbat complet modul în care definesc configurațiile. Hai să le luăm la bani mărunți prin 4 cazuri concrete pe care le întâlnesc zilnic.
1. Adnotarea explicită: Contractul rigid
Când scrii const user: User = { ... }, îi spui TypeScript-ului: „Vreau ca acest obiect să fie EXACT de tipul User”. E excelent pentru prinderea erorilor imediat, dar are un defect major: pierzi informația specifică despre valorile literale.
Acum vreo 3 luni, lucram la un sistem de teme. Dacă defineam tema ca const colors: Record<string, string> = { primary: '#00f' }, în momentul în care încercam să folosesc colors.primary, TS știa doar că e un string oarecare. Dacă greșeam și scriam colors.prymary, primeam eroare la runtime, nu la compilare, pentru că adnotarea explicită a „lărgit” tipul prea mult.
2. Operatorul as: Minciuna prin omisiune
Folosesc as (Type Assertion) extrem de rar, de obicei doar când interacționez cu librării externe prost tipizate sau la teste. Problema cu as este că e o minciună: „Crede-mă pe cuvânt, știu eu mai bine”.
Am văzut bug-uri de producție unde cineva a făcut data as User, dar API-ul a returnat null pentru un câmp obligatoriu. TypeScript nu a zis nimic pentru că i s-a ordonat să tacă. Dacă te regăsești scriind as des, probabil ai o problemă de design în schema de date.
3. Operatorul satisfies: Cel mai bun din ambele lumi
Introdus în TS 4.9, satisfies este „sweet spot”-ul. Verifică dacă un obiect respectă o interfață, dar păstrează cel mai specific tip posibil (inferența).
Pe proiectul curent, îl folosim pentru rutele de navigare. Vrem să ne asigurăm că fiecare rută are un path și o componentă, dar vrem și ca TypeScript să știe exact ce string-uri literale avem în obiect, ca să ne dea autocomplete când facem router.push(). Cu satisfies, dacă adaugi o cheie nouă care nu e în interfață, primești eroare, dar dacă e corect, TS reține valorile exacte, nu doar tipul lor general.
4. Ghid decizional: Ce alegem și când?
Iată cum tranșez eu lucrurile la noi în echipă:
- Folosește adnotări (
: T) pentru valorile returnate de funcții și pentru variabilele simple unde vrei să impui o formă fixă și nu te interesează valorile literale. - Folosește
satisfiespentru obiecte de configurare, teme, dicționare de rute sau orice loc unde vrei validare față de un tip, dar ai nevoie de inferență precisă pentru uz ulterior. - Folosește
asdoar ca ultimă soluție (DOM elements, JSON.parse de încredere) sau în unit teste. Dacă poți folosi un Type Guard (funcțieisType), e de preferat în loc deas.
În concluzie, dacă vrei un cod robust, învață să iubești inferența. TypeScript e mult mai deștept decât îi dăm credit, iar satisfies e instrumentul care ne permite să-i exploatăm inteligența fără să pierdem siguranța tipizării.