Treci la conținutul principal

Tipuri condiționale în TypeScript

Un tip condițional este un tip care depinde de alt tip. Funcționează ca o instrucțiune if-else, dar pentru tipuri în loc de valori.


Înțelegerea tipurilor condiționale în TypeScript

Tipurile condiționale îți permit să construiești tipuri care se schimbă în funcție de tipul pe care îl primesc. Ele fac posibile transformările avansate de tipuri și programarea la nivel de tipuri.

Concepte cheie:

  • Logică la nivel de tipuri: rulează o condiție pe un tip
  • Inferența tipurilor: descompune tipurile cu infer
  • Compunere: combină-le cu alte facilități TypeScript
  • Tipuri utilitare: construiește ajutoare de tip reutilizabile

Cazuri de utilizare frecvente:

  • Supraîncărcarea funcțiilor cu siguranță de tip
  • Transformarea tipurilor de răspuns ale unui API
  • Validare complexă a tipurilor
  • Utilitare de tip reutilizabile
  • Inferență avansată a tipurilor

Sintaxa de bază a tipurilor condiționale

Un tip condițional are forma T extends U ? X : Y. Citește-l astfel: dacă tipul T extinde (este asignabil la) tipul U, folosește tipul X, altfel folosește tipul Y.

Exemplu

IsString<T> devine true pentru string și pentru literalul "hello", deoarece un literal extinde tipul său de bază. Pentru number devine false. Și variabilele pot folosi tipul rezultat.

Prompt
Prezintă sintaxa de bază a tipurilor condiționale T extends U ? X : Y. Declară IsString<T> ca T extends string ? true : false, apoi aliasurile Result1, Result2 și Result3 pentru string, number și literalul "hello", cu comentarii care dau true, false, true, și două variabile a: IsString<string> și b: IsString<number> cu comentarii care notează tipurile lor 'true' și 'false'. Doar tipuri, nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Tipuri condiționale cu uniuni

Tipurile condiționale sunt deosebit de utile împreună cu tipurile uniune. Când tipul verificat este o uniune, condiția se aplică separat fiecărui membru. Acest lucru se numește tip condițional distributiv.

Tipuri condiționale distributive

ToArray<string | number> este împărțit în ToArray<string> | ToArray<number>, ceea ce dă string[] | number[]. Aceeași distribuție îi permite lui ExtractString să păstreze doar membrii de tip string ai unei uniuni, mapându-i pe ceilalți la never.

Prompt
Prezintă cum un tip condițional se distribuie peste membrii unei uniuni. Declară ToArray<T> ca T extends any ? T[] : never și StringOrNumberArray = ToArray<string | number> cu comentarii care explică faptul că devine string[] | number[], apoi ExtractString<T> care păstrează doar membrii de tip string și StringsOnly aplicat pe string | number | boolean | "hello" cu un comentariu care dă string | "hello". Doar tipuri, nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Inferența tipurilor cu infer

Cuvântul cheie infer declară o variabilă de tip în interiorul condiției unui tip condițional. Dacă condiția se potrivește, variabila conține tipul capturat și îl poți folosi în ramura adevărată.

Extragerea tipurilor din structuri complexe

ReturnType capturează tipul returnat R al unui tip de funcție. ElementType capturează tipul elementelor U al unui tip tablou. Cu typeof greet poți trimite tipul unei funcții reale către aceste ajutoare.

Prompt
Prezintă cuvântul cheie infer care capturează un tip returnat și tipul elementelor unui tablou. Declară ReturnType<T> ca T extends (...args: any[]) => infer R ? R : never, două funcții greet() care returnează "Salut, lume!" și getNumber() care returnează 42, și aliasurile GreetReturnType/NumberReturnType folosind typeof, cu comentarii care dau string și number. Apoi declară ElementType<T> care deduce U din (infer U)[] și aliasuri pentru number[] și string[]. Doar tipuri, nu se afișează nimic; fă din fișier un modul, astfel încât ReturnType local să nu intre în conflict cu cel global.
Ieșire: program TypeScript și rezultatul capturat

Tipuri condiționale predefinite

Biblioteca standard a TypeScript include deja mai multe tipuri condiționale, așa că nu trebuie să le scrii singur pe cele obișnuite.

Utilitare din biblioteca standard

Extract păstrează membrii unei uniuni care se potrivesc, Exclude îi elimină, NonNullable înlătură null și undefined, iar Parameters și ReturnType descompun un tip de funcție.

Prompt
Prezintă tipurile utilitare condiționale predefinite din biblioteca standard. Declară OnlyStrings = Extract<string | number | boolean, string>, NoStrings = Exclude<string | number | boolean, string>, NotNull = NonNullable<string | null | undefined>, Params = Parameters<(a: string, b: number) => void> și Return = ReturnType<() => string>, fiecare cu un comentariu care explică utilitarul și tipul rezultat. Doar tipuri, nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Tipare și tehnici avansate

Tipurile condiționale se pot apela pe ele însele, pot fi înlănțuite ca o scară if-else și pot descrie tipul returnat al unei funcții generice.

Tipuri condiționale recursive

Un tip condițional se poate referi la sine însuși. UnwrapPromise continuă să despacheteze până când tipul nu mai este un Promise, astfel încât chiar și o promisiune aflată într-o promisiune se rezolvă la tipul interior simplu.

Prompt
Prezintă un tip condițional recursiv care despachetează în profunzime tipurile Promise. Declară UnwrapPromise<T> ca T extends Promise<infer U> ? UnwrapPromise<U> : T, apoi aliasurile A, B și C pentru Promise<string>, Promise<Promise<number>> și boolean cu comentarii care dau string, number și boolean. Doar tipuri, nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Lanțuri if-else la nivel de tipuri

Mai multe condiții pot fi înlănțuite, una după alta. TypeName verifică un tip, pe rând, față de string, number, boolean, undefined și Function, iar în lipsa unei potriviri revine la "object".

Prompt
Prezintă un lanț de tipuri condiționale care se comportă ca o scară if-else la nivel de tipuri. Declară TypeName<T> care mapează string, number, boolean, undefined și Function la literalele de tip șir "string", "number", "boolean", "undefined" și "function", revenind la "object". Adaugă aliasurile de la T0 la T4 pentru string, 42, true, () => void și Date[] cu comentarii care arată rezultatele. Doar tipuri, nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Tipuri returnate condiționale

Tipurile condiționale strălucesc în utilitarele generice și în bibliotecile cu siguranță de tip. Aici, tipul returnat al unei funcții generice depinde de tipul argumentului ei. În interiorul funcției este nevoie de as any, deoarece TypeScript nu poate verifica corpul față de un tip returnat condițional.

Prompt
Prezintă o funcție generică al cărei tip returnat este un tip condițional bazat pe argumentul ei. Scrie processValue<T>(value: T) care returnează string pentru șiruri, number pentru numere, boolean pentru valori booleene și never în rest; transformă șirurile în majuscule, dublează numerele, neagă valorile booleene (fiecare cu o aserțiune as any) și aruncă Tip nesuportat pentru orice altceva. Apeleaz-o cu "hello", 10 și true, stocând stringResult, numberResult și boolResult cu comentarii care notează "HELLO", 20 și false. Nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Bune practici

Ce să faci:

  • Folosește tipuri condiționale pentru transformări complexe de tipuri
  • Combină-le cu infer pentru a extrage părți dintr-un tip
  • Creează utilitare de tip reutilizabile
  • Documentează tipurile condiționale complexe
  • Testează cazurile limită ale definițiilor tale de tipuri

Ce să nu faci:

  • Nu recurge la tipuri condiționale complexe atunci când un tip simplu este suficient
  • Nu construi tipuri condiționale adânc imbricate, greu de citit
  • Nu uita că tipurile foarte complexe încetinesc compilatorul
  • Nu folosi tipuri condiționale pentru logica de la rulare — ele există doar la compilare

Considerații de performanță:

  • Tipurile condiționale adânc imbricate cresc timpii de compilare
  • Stochează rezultatele intermediare în aliasuri de tip
  • Ține cont de limitele de adâncime a recursivității din TypeScript