eduardweb.
TypeScriptÎncepător#typescript#frontend#clean-code#backend

Utility types în practică: Cum am eliminat 350 de linii de cod redundant în TypeScript

De Ioana Marinescu, 3 aug. 2026 · 5 vizualizări · 2 like-uri

Postat 3 aug. 2026
typescript
interface User {
  id: string;
  email: string;
  passwordHash: string;
  createdAt: Date;
}

// Scăpăm de passwordHash pentru răspunsul public din API
type PublicUser = Omit<User, 'passwordHash'>;

// Request-ul de PATCH unde toate câmpurile devin opționale
type UpdateUserDto = Partial<PublicUser>;

// Extragere de tip direct dintr-o funcție helper fără interfețe extra
function setupSession() {
  return {
    token: "abc-123-xyz",
    expiresAt: new Date()
  };
}

type Session = ReturnType<typeof setupSession>;

Salutare. Anul trecut am preluat un codebase la o aplicație SaaS cu peste 12k de useri activi, unde am găsit vreo 15 interfețe diferite doar pentru entitatea de User. UserLogin, UserRegister, UserProfile, UserDB — toate scrise de mână, copy-paste pur. Am curățat vreo 350 de linii de cod redundant într-o după-amiază doar folosind utilitarele native din TypeScript.

Pick și Omit — scapă de interfețe duplicate

Cea mai frecventă greșeală pe care o văd la devii juniori este re-declararea tipurilor când au nevoie doar de o parte din proprietăți. Pui User în bază de date cu passwordHash, dar în API-ul public nu vrei să trimiți hash-ul sub nicio formă.

În loc să creezi o interfață nouă PublicUser de la zero, folosești Omit<User, 'passwordHash'>. E curat și expresiv. Dificultatea apare când ai obiecte mari și folosești Pick pentru a selecta strict două-trei câmpuri, de exemplu id și email pentru un dropdown simplu.

Trade-off-ul e că leagă strâns tipurile derivate de modelul principal. Dacă redenumești un câmp în interfața de bază, TypeScript îți va da eroare imediat peste tot unde ai folosit Pick sau Omit. Pentru mine ăsta e un plus uriaș la refactoring, dar trebuie să fii atent când schimbi structura primară.

Partial și Required — pentru formulare și PATCH-uri

Când construiești un endpoint de PATCH /users/:id, clientul nu trimite tot obiectul, ci doar câmpurile pe care vrea să le modifice. Aici intră în scenă Partial<User>, care face toate proprietățile opționale.

Atenție însă la un minus peste care m-am lovit des: Partial este "shallow". Dacă ai un obiect imbricat, de exemplu user.settings.theme, Partial nu va face și proprietățile din settings opționale, ci doar referința către obiectul settings. Pentru structuri adânci ai nevoie de un tip custom DeepPartial, dar pentru 90% din DTO-urile clasice, cel nativ își face treaba perfect.

La polul opus avem Required<T>. E foarte util când primești un obiect de configurare cu proprietăți opționale de la utilizator, dar în interiorul unui modul vrei să aplici defaults și să garantezi că toate câmpurile există, fără să pui if (config.timeout) la fiecare pas.

ReturnType — salvarea ta când lucrezi cu librării terțe

Asta e preferata mea când folosesc SDK-uri externe sau factory functions complexe. Să zicem că o librărie exportă o funcție createClient(), dar autorii au uitat să exporte explicit și tipul obiectului pe care îl returnează.

Fără ReturnType ar trebui să scrii tu manual o interfață paralelă și să o menții la fiecare update de librărie. Cu tipul nativ scrii doar type Client = ReturnType<typeof createClient> și ai rezolvat problema instant.

Am economisit zeci de ore la integrarea unui SDK de plăți procesând tipurile așa, mai ales la webhook-uri unde payload-ul era inferat automat dintr-o funcție de validare.

Utility types nu sunt doar sintaxă elegantă, ci scad direct numărul de bug-uri la refactoring. Voi cât de des le folosiți în proiecte sau încă preferați interfețe separate pentru fiecare use-case?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.