interface User {
id: string;
name: string;
email: string;
avatarUrl: string | null;
passwordHash: string;
role: 'admin' | 'user';
}
// Folosim Pick pentru componenta de UI
type UserCardProps = Pick<User, 'name' | 'avatarUrl'>;
// Folosim Omit pentru a trimite datele în siguranță pe client
type UserDto = Omit<User, 'passwordHash'>;
// Folosim Partial pentru update, dar păstrăm ID-ul obligatoriu
type UpdateUserPayload = Partial<Omit<User, 'id'>> & { id: User['id'] };Ne batem capul zilnic cu duplicarea tipurilor în TypeScript doar pentru că o API ne cere un subset de date sau o funcție are nevoie de parametri opționali. Am văzut zeci de proiecte unde oamenii rescriu interfețe cap-coadă în loc să folosească utilitarele native. Hai să vedem cum cureți codul ăsta rapid folosind Pick, Omit, Partial și ReturnType pe cazuri reale din producție.
Pick și Omit: Cum scapi de interfețele duplicate
La un proiect trecut, o aplicație de tip SaaS cu vreo 15.000 de utilizatori activi, aveam o entitate User imensă în baza de date. Avea peste 30 de câmpuri: de la hash-uri de parolă, metadata de Stripe, până la permisiuni și preferințe.
Pentru frontend, aveam nevoie de un simplu card de profil cu trei chestii: nume, avatar și email. La început, echipa crease o interfață nouă numită UserCardInfo. Ce s-a întâmplat? Când am schimbat tipul câmpului avatar din string în string | null în tipul de bază, aplicația s-a spart în producție pentru că am uitat să actualizăm și interfața duplicată.
Soluția a fost extrem de simplă. Am folosit Pick<User, 'name' | 'avatarUrl'>. Dacă tipul de bază se schimbă, eroarea apare acum la build, instantaneu. Omit funcționează fix invers. Îl folosești când vrei tot obiectul, mai puțin câteva chestii sensibile. De exemplu, când vrei să trimiți datele userului către client, dar fără passwordHash.
Partial și Required: Salvarea pentru endpoint-urile de PATCH
Când scrii o funcție pentru un endpoint de update, clientul trimite de obicei doar câmpurile pe care vrea să le schimbe. Nu vrei să-l obligi să trimită tot obiectul înapoi.
Aici intervine Partial. Acesta transformă toate proprietățile unei interfețe în opționale. Dar atenție la un trade-off destul de periculos: Partial face toate câmpurile opționale, inclusiv ID-ul entității. Or, tu ai nevoie de ID ca să știi pe cine updatezi în baza de date. Soluția pe care o folosesc eu în producție este să combin utilitarele, lăsând ID-ul obligatoriu și restul câmpurilor opționale.
ReturnType: Când n-ai chef să scrii tipul manual
Am dat de multe ori peste funcții fabrică (factory functions) care returnează obiecte complexe, cum ar fi un serviciu de conexiune la baza de date sau un client de API configurat.
În loc să scrii manual un tip gigantic pentru ce returnează acea funcție (care se poate schimba des în timpul dezvoltării), folosești ReturnType. E extrem de util pentru că TypeScript extrage automat tipul exact direct din semnătura funcției. Economisești timp și eviți desincronizările plictisitoare.
Trade-off: Unde devine treaba urâtă
Utilitarele astea sunt excelente, dar nu trebuie să faci abuz. Am văzut monstruozități în cod de genul Partial<Omit<Pick<User, 'id' | 'name' | 'role'>, 'role'>>. Este pur și simplu ilizibil pentru un coleg nou în echipă sau chiar pentru tine peste 6 luni.
Dacă ai nevoie de mai mult de două utilitare înlănțuite, oprește-te. Redefinește o interfață nouă, curată și explicită. Codul mai explicit este aproape întotdeauna mai ușor de întreținut pe termen lung decât codul excesiv de „inteligent”.
Voi ce utilitare folosiți cel mai des în proiectele de zi cu zi? Vă limitați la cele de bază sau folosiți și chestii mai avansate?