Menu

Mapped types in TypeScript: sintassi, modificatori e rimappatura

Un mapped type costruisce un nuovo tipo oggetto scorrendo delle chiavi: { [K in keyof T]: ... }. Impara la sintassi, i modificatori readonly e ? con + e -, la rimappatura delle chiavi con as, come filtrare le chiavi e come sono scritti Partial, Readonly, Required, Pick e Record.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Un mapped type costruisce un nuovo tipo oggetto scorrendo un insieme di chiavi. { [K in keyof T]: boolean } significa "per ogni chiave K di T, una proprietà di nome K e tipo boolean":

Aggiungi una chiave a Features e anche Flags<Features> la richiede. I mapped types esistono solo in fase di compilazione: descrivono oggetti, non li creano.

La sintassi

Un mapped type ha tre parti: una variabile per la chiave, un'unione di chiavi su cui iterare e il tipo della proprietà, che può usare la variabile della chiave.

type MappedType = {
  [K in Keys]: PropertyType; // K takes each member of Keys in turn
};

Keys può essere qualsiasi unione di stringhe, numeri o symbol. Non deve per forza venire da keyof:

È esattamente ciò che fa Record<Size, number>; Record è un mapped type di una riga. Il tipo della proprietà può usare la chiave: { [K in keyof T]: T[K] } copia il tipo di ogni proprietà tramite un indexed access, e { [K in keyof T]: T[K] | null } rende nullable ogni proprietà.

Modificatori: readonly e ? con + e -

Un mapped type può aggiungere o togliere i modificatori readonly e opzionale (?) su tutte le proprietà in un colpo solo. Il prefisso + aggiunge (è il comportamento predefinito se non scrivi nulla) e - toglie:

L'ultima riga mostra che readonly è solo una regola della fase di compilazione: l'assegnazione è stata segnalata (e qui soppressa con @ts-expect-error), ma il JavaScript generato l'ha eseguita comunque. -? fa più che togliere il punto interrogativo: toglie anche undefined dal tipo della proprietà, quindi AllRequired<Account> rifiuta { id: 1, email: undefined } con error TS2322: Type 'undefined' is not assignable to type 'string'.

Come sono fatti Partial, Readonly, Pick e Record

I tipi di utilità integrati che rimodellano gli oggetti sono mapped types. Queste sono le definizioni prese dal lib.es5.d.ts di TypeScript:

type Partial<T> = { [P in keyof T]?: T[P] };
type Required<T> = { [P in keyof T]-?: T[P] };
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Record<K extends keyof any, T> = { [P in K]: T };

Leggerle è un buon esercizio: Pick itera solo sulle chiavi che passi, e Record ignora qualsiasi tipo di partenza e dà a ogni chiave lo stesso tipo di valore. Omit non è un mapped type a sé: è Pick<T, Exclude<keyof T, K>>. Il riferimento sugli utility types li elenca tutti.

Rimappare le chiavi con as

Una clausola as dopo la chiave cambia il nome della proprietà. Insieme ai template literal types, può generare nomi nuovi a partire da quelli vecchi:

string & K serve perché keyof T può includere chiavi number e symbol, e Capitalize accetta solo stringhe. L'intersezione tiene le chiavi stringa e scarta le altre.

Filtrare le chiavi con never

Se la clausola as produce never per una chiave, quella chiave viene rimossa. Con un conditional type puoi tenere o scartare proprietà in base al loro tipo:

Tipi come DataOnly sono utili per descrivere cosa sopravvive a JSON.stringify o cosa modifica un form: i campi senza i metodi.

Quali modificatori vengono mantenuti

Un mapped type su keyof T per un tipo T si chiama omomorfo, e copia da T i modificatori readonly e ? di ogni proprietà. Un mapped type su una semplice unione di chiavi parte senza modificatori:

type Account = { readonly id: number; email?: string };

type Copy<T> = { [K in keyof T]: T[K] };
type A = Copy<Account>;
// { readonly id: number; email?: string }  (modifiers kept)

type B = { [K in "id" | "email"]: Account[K] };
// { id: number; email: string | undefined }  (email is required now)

B ha ancora undefined nel tipo di email, perché Account["email"] lo include, ma la proprietà in sé non è più opzionale: { id: 1 } viene rifiutato. Ecco perché Partial<T> e simili mappano su keyof T: mantengono tutto ciò che non cambiano esplicitamente.

Un mapped type omomorfo generico applicato a un array o a una tupla produce un array o una tupla, non un oggetto con chiavi numeriche. Readonly<string[]> è readonly string[], e con type Box<T> = { [K in keyof T]: { value: T[K] } }, Box<[string, number]> è [{ value: string }, { value: number }].

Mapped types e codice a runtime

Dato che un mapped type descrive soltanto un oggetto, una funzione che costruisce un oggetto del genere ha bisogno di un proprio ciclo a runtime. Il tipo descrive poi il suo risultato:

Le callback ricevono i tipi dei parametri dal mapped type (v è string in una e number nell'altra) senza nessuna annotazione. Se cercavi la classe Map e non la mappatura dei tipi, vedi Map in TypeScript.

Domande frequenti

Cos'è un mapped type in TypeScript?

Un tipo che crea un tipo oggetto iterando su un'unione di chiavi: { [K in Keys]: SomeType }. Di solito le chiavi sono keyof T, quindi il nuovo tipo ha le stesse chiavi di T con i tipi delle proprietà trasformati, come in type Flags<T> = { [K in keyof T]: boolean }.

Cosa significano +readonly, -readonly, +? e -? in un mapped type?

Aggiungono o rimuovono modificatori. readonly o +readonly rende readonly ogni proprietà, -readonly toglie readonly. ? o +? rende opzionale ogni proprietà, -? le rende obbligatorie e toglie anche undefined dai loro tipi. Required<T> è scritto con -?.

Come rinomino le chiavi in un mapped type?

Usa una clausola as dopo la chiave: { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] } trasforma name in getName. Se la clausola as mappa una chiave a never, la chiave viene rimossa: è così che si filtrano le proprietà.

Un mapped type è la stessa cosa di Map in TypeScript?

No. Un mapped type è una trasformazione di tipi in fase di compilazione e non produce codice. Map<K, V> è la classe Map di JavaScript, una collezione di coppie chiave valore a runtime. Chi cerca "typescript map type" spesso intende l'una o l'altra cosa.

Come sono implementati Partial e Readonly?

Come mapped types nella libreria standard: type Partial<T> = { [P in keyof T]?: T[P] } e type Readonly<T> = { readonly [P in keyof T]: T[P] }. Anche Pick e Record sono mapped types; Omit è Pick combinato con Exclude.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA