eduardweb.
TypeScript avansatIntermediar#architecture#typescript#frontend#clean-code

De ce nu mai scriu cod TypeScript fără Discriminated Unions

De Alexandru Matei, 10 iun. 2026 · 2 vizualizări · 3 like-uri

Postat acum 5 zile
typescript
type ApiResponse<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T; updatedAt: Date }
  | { status: 'error'; error: Error };

function handleResponse<T>(state: ApiResponse<T>) {
  switch (state.status) {
    case 'success':
      // TypeScript știe sigur că aici avem data și updatedAt
      return `Data: ${JSON.stringify(state.data)} (Updated: ${state.updatedAt.toISOString()})`;
    case 'error':
      // Aici știe că avem error de tip Error
      return `Error: ${state.error.message}`;
    default:
      return 'Loading or Idle...';
  }
}

Salutare. În articolul ăsta scurt îți arăt cum să scapi de erorile de tip „undefined is not a function” sau „cannot read property of undefined” în TypeScript folosind discriminated unions. O să vezi exact cum aplic pattern-ul ăsta la API responses, state machines și reducer actions ca să scriu cod pe care compilatorul îl verifică la sânge în locul meu.

Problema clasică cu proprietățile opționale

Cu toții am făcut asta la început. Când ai de modelat starea unui request HTTP, primul impuls e să pui toate proprietățile posibile într-o singură interfață și să le faci opționale. Ceva de genul isLoading: boolean, data?: T, error?: string.

Pare inofensiv, dar e o capcană. Am avut acum vreo trei ani un proiect cu un dashboard complex, cam 40 de ecrane diferite, unde foloseam abordarea asta. Ne-am trezit cu zeci de bug-uri în producție pentru că uitam să verificăm dacă data chiar există atunci când isLoading era false. Compilatorul TypeScript nu avea cum să ne ajute, fiindcă din punctul lui de vedere, data putea fi undefined oricând, indiferent de valoarea lui isLoading. Am pierdut zeci de ore făcând debugging inutil și am decis că trebuie să schimbăm abordarea.

Soluția: Discriminated Unions

Ideea e simplă: în loc de un singur obiect mare cu proprietăți opționale, definim mai multe tipuri stricte care au în comun o singură proprietate literară, numită „discriminant” sau „tag”. De obicei folosesc status sau type.

TypeScript e suficient de deștept încât, în momentul în care verifici valoarea acelei proprietăți comune într-un if sau switch, face automat „narrowing” (îngustarea tipului). Știe exact ce alte proprietăți sunt disponibile în acel bloc de cod. Nu mai ai nevoie de operatorul ? sau de aserțiuni de tip forțate cu as.

Unde folosesc pattern-ul ăsta zi de zi

Pe lângă răspunsurile din API-uri, folosesc uniunile discriminate la reducer-ele din React (useReducer sau Redux ToolKit). Fiecare acțiune are un type diferit și un payload specific. Dacă acțiunea este 'LOGOUT', compilatorul știe că nu are payload. Dacă e 'LOGIN_SUCCESS', știe că payload-ul trebuie să conțină neapărat obiectul user.

O altă zonă excelentă sunt micile automate de stări (state machines). Gândește-te la un player video. Stările lui pot fi idle, playing, paused, buffering. Când ești în starea playing, ai nevoie de currentTime și playbackRate. În starea idle, aceste date nu au sens. Folosind o uniune discriminată, e imposibil să accesezi currentTime dacă player-ul este oprit.

Trade-off-ul sincer: când devine enervant?

Nimic nu e perfect pe lumea asta și există și un revers al medaliei. Pattern-ul ăsta funcționează de minune când controlezi tu structura datelor sau când lucrezi cu un backend modern.

Dar dacă lucrezi cu un API legacy, scris prost, care îți trimite și data și error în același timp în același payload, maparea acelor date brute în uniuni discriminate pe frontend poate deveni un calvar. Te trezești că trebuie să scrii o tonă de „type guards” manuale sau cod de conversie (boilerplate) doar ca să mulțumești compilatorul TypeScript. Uneori, pentru chestii extrem de simple, e pur și simplu prea mult cod de scris.

Voi cum gestionați stările asincrone în aplicațiile voastre? Mergeți pe varianta clasică cu proprietăți opționale sau folosiți tagged unions?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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