Treci la conținutul principal

Tipuri avansate în TypeScript

TypeScript are un sistem de tipuri avansat. El îți permite să descrii cu precizie relații complexe între tipuri.

Aceste facilități te ajută să construiești aplicații robuste, ușor de întreținut și ferite de multe erori de tipuri.


Tipurile avansate din TypeScript

Această pagină oferă o prezentare generală a principalelor facilități pentru tipuri avansate:

  • Tipuri mapate: transformă proprietățile unui tip existent
  • Tipuri condiționale: aleg un tip în funcție de o condiție
  • Tipuri literale de șablon: construiesc tipuri șir de caractere cu sintaxa de șablon
  • Tipuri utilitare: ajutoare predefinite pentru transformările obișnuite
  • Tipuri recursive: tipuri care se referă la ele însele, pentru date de tip arbore
  • Gardieni de tip și predicate de tip: verificări care restrâng un tip în timpul execuției
  • Deducerea tipurilor: potrivirea de tipare în interiorul tipurilor, cu infer

Tipuri mapate

Un tip mapat creează un tip nou parcurgând proprietățile unui tip existent și transformând fiecare proprietate.

Tip mapat de bază

Un singur șablon transformă fiecare proprietate a unui tip obiect într-un tip nou. Aici fiecare proprietate a lui User devine boolean.

Prompt
Arată un tip mapat de bază, care transformă în boolean fiecare proprietate a unui tip obiect. Declară un tip generic Flags<T> mapat peste keyof T, o interfață User cu id: number, name: string și email: string, și un alias UserFlags = Flags<User>; adaugă un comentariu care arată tipul obiect echivalent. Fișierul doar declară tipuri și nu afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Modificatori în tipurile mapate

În timpul mapării poți adăuga sau elimina modificatorii readonly și ? (opțional) pentru fiecare cheie. Un + adaugă un modificator, iar un - îl elimină.

Prompt
Arată cum un tip mapat adaugă sau elimină modificatorii readonly și ?. Declară o interfață Todo (title: string, description: string, completed: boolean), un tip mapat OptionalTodo care face opționale toate proprietățile, un tip generic Concrete<T> care elimină și readonly, și ? prin -readonly și -?, și un ReadonlyRequired<T> care adaugă +readonly și elimină ?. Doar declarații de tipuri, fără rezultat afișat.
Ieșire: program TypeScript și rezultatul capturat

Redenumirea cheilor

Cu clauza as poți redenumi cheile în timpul mapării sau le poți elimina mapându-le la never. Ajutoare pentru șiruri de caractere precum Capitalize și verificările condiționale fac acest lucru posibil.

Prompt
Arată redenumirea cheilor într-un tip mapat cu ajutorul clauzei as. Declară o interfață User (id: number, name: string, email: string), un tip generic Getters<T> care redenumește fiecare cheie în get plus numele cheii cu inițială majusculă (folosind Capitalize<string & K>), cu valoarea () => T[K], un alias UserGetters = Getters<User> cu un comentariu care arată forma rezultată, și un tip MethodsOnly<T> care păstrează doar cheile ale căror valori extind Function, mapându-le pe celelalte la never. Doar tipuri, nu afișează nimic.
Ieșire: program TypeScript și rezultatul capturat

Tipuri condiționale

Un tip condițional alege între două tipuri în funcție de o condiție. Funcționează ca o instrucțiune if, dar la nivelul tipurilor.

Tipuri condiționale de bază

Forma T extends U ? X : Y alege X atunci când T poate fi atribuit lui U, iar altfel alege Y. Verificarea are loc în întregime la nivelul tipurilor.

Prompt
Arată un tip condițional de bază și unul care folosește infer. Declară IsString<T> ca T extends string ? true : false, apoi aliasurile A, B, C, D pentru string, number, literalul 'hello' și string | number, cu comentarii care indică rezultatele true, false, true și boolean. Declară și ArrayElement<T>, care deduce tipul elementelor U dintr-un tip tablou, plus un alias Numbers = ArrayElement<number[]>. Doar tipuri, fără rezultat afișat.
Ieșire: program TypeScript și rezultatul capturat

Cuvântul cheie Infer

În interiorul condiției unui tip condițional, infer introduce o nouă variabilă de tip și captează o parte din tipul verificat. Așa funcționează ajutoarele predefinite ReturnType și Parameters.

Prompt
Arată cum cuvântul cheie infer captează părți dintr-un tip în interiorul unui tip condițional, recreând patru tipuri utilitare predefinite. Declară ReturnType<T> (deduce tipul returnat R al unui tip funcție, altfel any), Parameters<T> (deduce tuplul parametrilor P, altfel never), ConstructorParameters<T> restrâns la tipuri constructor și care deduce tuplul parametrilor constructorului, și InstanceType<T>, care deduce tipul instanței dintr-un constructor. Doar tipuri, nu se afișează nimic; transformă fișierul într-un modul, ca numele să nu intre în conflict cu cele globale predefinite.
Ieșire: program TypeScript și rezultatul capturat

Tipuri condiționale distribuite

Atunci când tipul verificat este un simplu parametru de tip, tipul condițional se aplică separat fiecărui membru al reuniunii. Acest lucru se numește distribuire. Încadrarea tipului într-un tuplu, precum [T] extends [any], oprește distribuirea, astfel încât reuniunea este tratată ca un singur tip.

Prompt
Arată cum se distribuie tipurile condiționale peste tipurile reuniune și cum poate fi oprită distribuirea. Declară ToArrayNonDist<T> ca T extends any ? T[] : never și StrOrNumArr = ToArrayNonDist<string | number>, apoi ToArray<T> scris cu tuplul de încadrare [T] extends [any] ? T[] : never și StrOrNumArr2 = ToArray<string | number>, fiecare cu un comentariu care arată tipul rezultat. La final declară FilterStrings<T>, care păstrează doar membrii de tip șir de caractere, și un alias Letters aplicat lui 'a' | 'b' | 1 | 2 | 'c'. Doar tipuri, fără rezultat afișat.
Ieșire: program TypeScript și rezultatul capturat

Tipuri literale de șablon

Tipurile literale de șablon construiesc tipuri noi de șiruri de caractere folosind aceeași sintaxă cu accent grav ca șirurile șablon din JavaScript.

Tipuri literale de șablon de bază

Un tip literal de șablon limitează un șir de caractere la un tipar. Combinat cu reuniuni, produce toate combinațiile posibile ale părților. Atribuirea unui șir care nu se potrivește cu tiparul este o eroare de compilare.

Prompt
Arată tipurile literale de șablon de bază și eroarea de compilare pe care o depistează. Declară un tip Greeting egal cu șablonul `Hello, ${string}`, atribuie valoarea validă 'Hello, World!' lui validGreeting și atribuie 'Hi there!' lui invalidGreeting, marcat cu // Eroare, astfel încât fișierul să nu treacă intenționat verificarea tipurilor. Apoi declară reuniunile Color ('red' | 'green' | 'blue') și Size ('small' | 'medium' | 'large') și un tip șablon Style egal cu `${Color}-${Size}`, cu un comentariu care enumeră cele nouă valori rezultate. Rezultat așteptat: o eroare de compilare pentru salutul nevalid.
Ieșire: program TypeScript și rezultatul capturat

Tipuri pentru manipularea șirurilor de caractere

TypeScript oferă patru ajutoare care schimbă felul scrierii pentru tipurile literale de șir: Uppercase, Lowercase, Capitalize și Uncapitalize. Ele sunt utile în interiorul tipurilor literale de șablon.

Prompt
Arată tipurile predefinite pentru manipularea șirurilor de caractere, aplicate unor literali de tip șir. Declară de la T1 la T4 folosind Uppercase<'hello'>, Lowercase<'WORLD'>, Capitalize<'typescript'> și Uncapitalize<'TypeScript'>, cu comentarii care arată rezultatele, apoi o reuniune EventType ('click' | 'change' | 'keydown') și un tip șablon EventHandler care adaugă prefixul on la numele evenimentelor scrise cu inițială majusculă. Doar tipuri, fără rezultat afișat.
Ieșire: program TypeScript și rezultatul capturat

Tipare avansate

Tipurile literale de șablon pot fi combinate cu infer și cu redenumirea cheilor. Astfel poți extrage informații dintr-un tip șir de caractere, precum parametrii unei rute, sau poți genera un întreg API de nume de gestionari pornind de la o hartă de evenimente.

Prompt
Arată tipare avansate cu tipuri literale de șablon, care combină infer și redenumirea cheilor. Declară un tip recursiv ExtractRouteParams<T> care se potrivește cu `${string}:${infer Param}/${infer Rest}` (și cu forma cu un singur parametru), pentru a construi un tip obiect cu câte o proprietate string pentru fiecare parametru al rutei, plus un alias Params pentru '/users/:userId/posts/:postId'. Apoi declară un tip EventMap (click cu numerele x/y, change ca string, keydown cu key: string și code: number) și un tip mapat EventHandlers care redenumește fiecare cheie în on plus numele cu inițială majusculă și îi dă tipul unui gestionar care primește EventMap[K]. Doar tipuri, fără rezultat afișat.
Ieșire: program TypeScript și rezultatul capturat

Tipuri utilitare

TypeScript vine cu multe tipuri utilitare predefinite. Ele acoperă cele mai obișnuite transformări de tipuri, ca să nu fie nevoie să le scrii singur.

Tipuri utilitare uzuale

Partial, Required, Readonly, Pick și Omit sunt cele pe care le vei folosi cel mai des. Poți citi și tipul unei singure proprietăți cu User['id'], precum și toate numele de chei cu keyof User.

Prompt
Arată cele mai des folosite tipuri utilitare predefinite, aplicate unei singure interfețe. Declară o interfață User cu id: number, name: string, email: string și createdAt: Date, apoi aliasurile PartialUser = Partial<User>, RequiredUser = Required<PartialUser>, ReadonlyUser = Readonly<User>, UserPreview = Pick<User, 'id' | 'name'> și UserWithoutEmail = Omit<User, 'email'>, fiecare cu un comentariu scurt. Încheie cu UserId = User['id'] și UserKeys = keyof User și cu comentarii care arată rezultatele. Doar tipuri, fără rezultat afișat.
Ieșire: program TypeScript și rezultatul capturat

Tipuri utilitare avansate

Exclude elimină membri dintr-o reuniune, iar Extract păstrează doar pe cei care se potrivesc. Poți scrie și propriile ajutoare, cum ar fi un tip Mutable care elimină readonly de la fiecare proprietate.

Prompt
Arată tipurile utilitare care filtrează reuniuni și un tip mapat scris de tine. Declară un tip condițional NonNullable<T> care transformă null | undefined în never, o reuniune Numbers egală cu 1 | 2 | 3 | 'a' | 'b', JustNumbers = Exclude<Numbers, string> și JustStrings = Extract<Numbers, string> cu comentarii care arată rezultatele, apoi tipurile obiect A (a: string; b: number; c: boolean) și B (a: string; b: number) și C = Omit<A, keyof B>, iar la final un tip mapat Mutable<T> care folosește -readonly. Doar tipuri, fără rezultat afișat; transformă fișierul într-un modul, ca NonNullable să nu intre în conflict cu cel global.
Ieșire: program TypeScript și rezultatul capturat

Tipuri recursive

Un tip recursiv se referă la el însuși în propria definiție. Aceasta este modalitatea naturală de a descrie date de tip arbore, în care un nod poate conține alte noduri de aceeași formă.

Tip recursiv de bază

Un nod de arbore binar conține nodurile opționale left și right, de același tip. O valoare JSON poate conține tablouri și obiecte cu alte valori JSON. Un comentariu conține un tablou de răspunsuri care sunt tot comentarii.

Prompt
Arată tipuri recursive de bază, care modelează date de tip arbore. Declară un tip generic BinaryTree<T> cu o valoare value și subarbori opționali left/right de același tip, o reuniune JSONValue formată din string, number, boolean, null, JSONValue[] și un obiect cu valori JSONValue, și un tip Comment cu id, content, un tablou replies: Comment[] și createdAt: Date. Doar tipuri, fără rezultat afișat; transformă fișierul într-un modul, ca Comment să nu intre în conflict cu tipul global din DOM.
Ieșire: program TypeScript și rezultatul capturat

Tipuri recursive avansate

Aceeași idee descrie o listă înlănțuită, un arbore de directoare ai cărui copii sunt fișiere sau alte directoare, o mașină cu stări ale cărei tranziții duc la alte stări și chiar un tip de funcție care se acceptă pe sine.

Prompt
Arată tipuri recursive mai avansate. Declară un tip generic LinkedList<T> cu value și next: LinkedList<T> | null, un tip File (type: 'file', name, size) și un tip Directory (type: 'directory', name, children: (File | Directory)[]), un tip State ale cărui transitions asociază numele evenimentelor cu alte State, și un tip de funcție RecursiveFunction<T> care acceptă T | RecursiveFunction<T>. Doar tipuri, fără rezultat afișat; transformă fișierul într-un modul, ca File să nu intre în conflict cu tipul global din DOM.
Ieșire: program TypeScript și rezultatul capturat

Bune practici

Când să folosești tipuri avansate:

  • Folosește tipuri mapate atunci când trebuie să schimbi dintr-odată multe proprietăți ale unui tip obiect
  • Folosește tipuri condiționale atunci când un tip depinde de alt tip
  • Folosește tipuri literale de șablon pentru tipare de șiruri de caractere și pentru potrivirea lor
  • Folosește tipuri utilitare pentru transformările obișnuite — preferă-le pe cele predefinite atunci când se potrivesc
  • Folosește tipuri recursive pentru date de tip arbore sau imbricate

Aspecte legate de performanță:

  • Tipurile recursive imbricate pe multe niveluri pot încetini compilatorul TypeScript
  • Tipurile reuniune foarte mari (peste 100 de membri) pot cauza probleme de performanță
  • Împarte tipurile complexe în aliasuri de tip mai mici

Capcane frecvente

Probleme legate de deducerea tipurilor:

  • Tipurile condiționale se distribuie peste tipurile reuniune, ceea ce poate fi surprinzător
  • infer se comportă diferit în funcție de locul în care este folosit
  • Unele tipuri utilitare nu funcționează bine cu any sau unknown

Ușurința întreținerii:

  • Folosirea excesivă a tipurilor complexe face codul greu de citit
  • Explică transformările complexe de tipuri prin comentarii
  • Pentru cazurile foarte complexe, o aserțiune de tip sau o funcție ajutătoare poate fi mai simplă