type ColorConfig = Record<string, string | number[]>;
// 1. Problema cu as: nu dă eroare deși lipsește ceva
const themeBad = { primary: '#ff0000' } as ColorConfig;
// 2. Problema cu adnotarea: colors.primary devine generic string | number[]
const themeGeneric: ColorConfig = { primary: '#ff0000' };
// 3. Soluția: satisfies verifică structura DAR păstrează tipul literal
const themeBest = {
primary: '#ff0000',
secondary: [255, 0, 0]
} satisfies ColorConfig;
// Aici TS știe sigur că e string, nu number[]
const hex = themeBest.primary.toUpperCase();Am tot văzut discuții pe Slack și pe forumuri despre noul operator satisfies și sincer, până acum vreun an, și eu aruncam cu as peste tot când mă grăbeam să închid un task. Dar după ce am pățit-o la un proiect cu vreo 12.000 de linii de cod, unde un Type Assertion mi-a ascuns un bug de runtime timp de două săptămâni, mi-am schimbat complet workflow-ul. Problema e că mulți dintre noi tratăm TypeScript-ul ca pe un bodyguard care ne lasă să trecem dacă-i arătăm o legitimație falsă, în loc să-l lăsăm să-și facă treaba de detectiv.
Cazul 1: Capcana Type Assertion (as)
Folosesc as doar când chiar n-am de ales, de exemplu la un API extern care returnează any și știu sigur ce e acolo. În rest, e periculos. Când scrii const user = {} as User, tu practic îi spui compilatorului: „Taci, știu eu mai bine”. Dacă interfața User se schimbă și adaugi un câmp obligatoriu, TypeScript nu o să urle la linia aia. O să afli că lipsește proprietatea abia când crapă aplicația în browser. Am economisit cam 20% din timpul de debugging la ultimul refactoring masiv eliminând peste 50 de astfel de aserțiuni care mascau date incomplete.
Cazul 2: Adnotările explicite și pierderea specificității
Aici e marea dilemă. Să zicem că ai un obiect de culori. Dacă pui const colors: Record<string, string> = { primary: '#000' }, ai câștigat validare, dar ai pierdut informație. Dacă încerci să accesezi colors.primary, TypeScript știe doar că e un string generic, nu că e exact culoarea aia. Mai rău, dacă scrii greșit colors.primry, s-ar putea să nu primești eroare dacă record-ul e prea permisiv. E o soluție sigură pentru interfețe de date, dar proastă pentru config-uri unde ai nevoie de tipuri literale.
Cazul 3: Satisfies - calea de mijloc inteligentă
Operatorul satisfies a apărut în TS 4.9 și e exact ce ne lipsea. Verifică dacă un obiect respectă o interfață, dar îi păstrează tipul cel mai specific posibil (inferat).
La un proiect cu 8k useri unde aveam o structură complexă de permisiuni, am trecut toate config-urile pe satisfies. Avantajul? Dacă uitam un câmp cerut de interfață, primeam eroare imediat (spre deosebire de as). Dar, dacă accesam o proprietate, IDE-ul știa exact ce valoare are, fără să facă „widening” la un tip generic. E un trade-off excelent: ai siguranța adnotării explicite, dar păstrezi flexibilitatea inferenței.
Cazul 4: Când să alegi ce?
După mii de ore de codat, am ajuns la o regulă simplă pentru echipa mea:
- Folosește adnotări explicite (
const x: Type) pentru variabile simple și return types la funcții. Vrei un contract clar acolo. - Folosește
satisfiespentru obiecte de configurare, rute, theme objects sau orice structură unde vrei să fii sigur că respecți un shape, dar vrei ca restul codului să „vadă” valorile exacte. - Folosește
asdoar pentru interacțiunea cu librării legacy sau când faci type narrowing manual și chiar nu există altă cale.
Personal, am redus build time-ul cu vreo 5% pe un proiect mare simplificând ierarhiile de tipuri și folosind mai mult inferența asistată de satisfies. Nu e mult, dar experiența de development e mult mai fluidă când nu te lupți cu compilatorul, ci colaborezi cu el.
Voi cum stați cu as-urile prin cod? Mai aveți „păcate” de genul ăsta prin fișierele de configurare?