eduardweb.
TypeScriptÎncepător#typescript#clean-code#web-development

Utility types în practică: Cum să nu mai scrii interfețe duplicat în TypeScript

De Bogdan Răducanu, 21 iun. 2026 · 17 vizualizări · 3 like-uri

Postat 21 iun. 2026
typescript
interface User {
  id: string;
  name: string;
  email: string;
  passwordHash: string;
  createdAt: Date;
  phoneNumber?: string;
}

// Folosim Omit pentru a ascunde datele sensibile
export type UserResponse = Omit<User, 'passwordHash'>;

// Folosim Pick pentru un payload specific
export type UserContact = Pick<User, 'name' | 'email'>;

// Folosim Partial pentru request-ul de update (PATCH)
export type UpdateUserPayload = Partial<Omit<User, 'id' | 'createdAt'>>;

// ReturnType ne ajută să extragem tipul returnat de o funcție
async function getUserStats(userId: string) {
  return {
    userId,
    loginCount: 42,
    lastActive: new Date(),
  };
}

export type UserStats = ReturnType<typeof getUserStats>;

Salutare! Astăzi vreau să vorbim despre cum eliminăm redundanța din TypeScript fără să ne complicăm viața. Dacă încă scrii de mână interfețe separate pentru crearea, actualizarea și afișarea aceleiași entități, pierzi timp și riști bug-uri stupide la fiecare refactoring.

La un proiect trecut, un SaaS de HR cu vreo 14.000 de utilizatori activi, aveam o entitate Employee cu vreo 35 de câmpuri. Când am adăugat un simplu câmp de slackId, a trebuit să modific manual în 5 fișiere diferite: în tipul de bază, în cel de payload pentru POST, în cel de PATCH, în cel de state din frontend și în mock-urile de test. O nebunie totală. Atunci am curățat totul folosind utility types și am salvat probabil ore bune de muncă la următoarele sprinturi.

Pick și Omit: Cum decupezi doar ce ai nevoie

Cele mai folosite instrumente din trusa mea sunt Pick și Omit. Numele lor sunt destul de clare: unul alege, celălalt ignoră.

Să luăm exemplul clasic. Ai o interfață mare pentru un User din baza de date, care conține id, nume, email, parolă hashuită și data creării. Când trimiți datele către frontend, evident că nu vrei să incluzi parola. În loc să creezi un nou tip UserPublic de la zero, folosești Omit:

type PublicUser = Omit<User, 'passwordHash'>;

Dacă în schimb ai nevoie doar de email și nume pentru o listă simplă, folosești Pick:

type UserContactInfo = Pick<User, 'email' | 'name'>;

Trade-off-ul sincer: Omit este extrem de comod, dar are un risc major de securitate. Dacă peste 3 luni un coleg adaugă un câmp sensibil în tipul User (să zicem, ssn sau backupCodes), acel câmp va fi inclus automat în PublicUser pentru că nu a fost omis explicit. De aceea, pentru API-uri publice, prefer adesea să fiu defensiv și să folosesc Pick, chiar dacă scriu mai mult cod.

Partial și Required: Soluția pentru update-uri (PATCH)

Când faci un update prin PATCH, utilizatorul poate trimite doar două câmpuri din treizeci. Aici intervine Partial, care face toate proprietățile opționale, scutindu-te de scris semne de întrebare la fiecare cheie.

Totuși, ce faci când vrei ca anumite câmpuri să devină obligatorii într-un anumit context, deși în baza de date ele sunt opționale (de exemplu, după ce userul trece de onboarding)? Folosești Required combinat cu Pick ca să forțezi prezența lor în controller-ul respectiv.

ReturnType: Când nu controlezi codul

Asta e preferata mea când lucrez cu librării externe sau generatoare de cod (cum ar fi Prisma sau tRPC). Uneori ai o funcție complexă care returnează un obiect gigant, dar librăria nu exportă direct tipul acelui obiect.

În loc să încerci să-l reconstruiești manual, lași TypeScript să-și facă magia. Tipurile derivate din funcții sunt mereu sincronizate.

Voi cum procedați? Preferați să fiți expliciți și să scrieți interfețe separate pentru fiecare payload, sau lăsați utility types să facă treaba grea?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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