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

Utility types în practică: cum am scăpat de 40 de interfețe duplicate

De Cristian Barbu, 25 sept. 2026 · 13 vizualizări · 3 like-uri

Postat acum 6 zile
typescript
interface User {
  id: string;
  name: string;
  email: string;
  avatarUrl?: string;
  role: 'admin' | 'editor' | 'viewer';
  createdAt: Date;
}

// 1. Pick: doar datele afișate pe profilul public
type PublicProfile = Pick<User, 'name' | 'avatarUrl'>;

// 2. Omit + Partial: payload pentru PATCH request (fără id și createdAt)
type UpdateUserPayload = Partial<Omit<User, 'id' | 'createdAt'>>;

// 3. Required: forțăm toate câmpurile pentru procesarea internă de avatar
type CompleteUser = Required<User>;

// 4. ReturnType: extragem automat forma unui rezultat complex
function makeUserSession(user: User) {
  return {
    token: `sess_${user.id}`,
    expiresAt: Date.now() + 3600 * 1000,
    permissions: user.role === 'admin' ? ['all'] : ['read']
  };
}

type UserSession = ReturnType<typeof makeUserSession>;

Când am început serios cu TypeScript prin 2016, făceam o greșeală clasică: copiam interfețe la nesfârșit. Aveam User, apoi făceam UserForm, apoi UserUpdatePayload, apoi UserTableItem, fiecare definită de la zero cu aceleași câmpuri. La un magazin online cu vreo 12k comenzi pe lună, am schimbat tipul unui ID din number în string și a trebuit să modific în 14 fișiere diferite doar ca să treacă build-ul.

Atunci am început să folosesc serios utility types. Nu sunt vreo magie avansată de tip generic pe care o vezi prin librării obscure, ci unelte de zi cu zi care te scapă de muncă de mântuială.

Pick și Omit: felierea modelelor fără copy-paste

Pick ia o interfață mare și păstrează doar cheile de care ai nevoie. Omit face exact invers: aruncă ce nu-ți trebuie și păstrează restul.

În practică, le folosesc cel mai des la formulare sau tabele. Dacă backend-ul îmi livrează un Product complet (cu id, createdAt, internalCost, stock, slug), pe pagina publică n-am nevoie de toate. În loc să inventez o interfață PublicProductProps pe care să o uit desincronizată peste două sprinturi, scriu pur și simplu un Omit<Product, 'internalCost' | 'stock'>.

Trade-off sincer: Omit e mai comod la început, dar devine periculos la refactoring. Dacă adaugi un câmp sensibil în tipul de bază — să zicem hashedPassword pe User —, el se va propaga automat în toate tipurile derivate cu Omit, dacă nu-ți aduci aminte să-l excluzi explicit. Pentru date expuse către client, Pick este aproape întotdeauna opțiunea mai sigură.

Partial și Required: request-uri de tip PATCH și configurări stricte

Partial trece toate proprietățile pe opțional (?). E aur curat pentru endpoint-uri de update parțial sau formulare multi-step unde utilizatorul completează datele pe rând.

Required e geamănul opus: ia un tip unde unele proprietăți sunt opționale și le forțează pe toate să existe. Eu îl folosesc frecvent când scriu funcții de configurare. Primești o opțiune de la utilizator unde câmpurile sunt parțiale, aplici niște valori default, iar funcția internă lucrează garantat cu Required<Config>. În felul ăsta, compiler-ul mă bate peste degete dacă uit să tratez o valoare implicită.

ReturnType: când inferența e mai deșteaptă ca tine

Când lucrezi cu funcții factory sau librării externe care returnează obiecte complexe, să scrii tipul manual e o pierdere de timp. ReturnType<typeof myFunction> extrage direct tipul produs de funcție.

Am avut cazul la un parser intern de rapoarte CSV. Funcția construia un obiect cu 20 de proprietăți calculate din mers. În loc să mențin un tip separat pentru acel rezultat (care se schimba săptămânal în funcție de cerințele de la business), am lăsat TypeScript să deducă ce scoate funcția, iar mai departe am propagat acel ReturnType în restul aplicației.

Nu te complica să definești zeci de interfețe similare dacă ai deja sursa de adevăr într-un singur loc. Tu câte tipuri duplicate ai în proiect chiar acum?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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