Omit<T, K> è un utility type integrato che crea un tipo oggetto con tutte le proprietà di T tranne le chiavi elencate in K. L'uso classico: un tipo User che ha una password, e una versione pubblica che non ce l'ha.
PublicUser resta sincronizzato con User: aggiungi un campo a User e compare anche in PublicUser, mentre password resta fuori. Omit agisce solo sui tipi; è la destrutturazione in toPublic a rimuovere la proprietà dall'oggetto vero e proprio.
Omettere più chiavi
Il secondo argomento è un'unione, quindi elenca tutte le chiavi che ti servono con |:
"Il tipo senza i campi generati dal server" è l'Omit più comune nel codice reale: un unico tipo di modello, con Omit che ricava la forma dell'input per i moduli di creazione e le richieste API.
Come è definito Omit
Omit è costruito a partire da altri due utility type:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Leggilo dall'interno: keyof T è l'unione di tutte le chiavi, Exclude<keyof T, K> toglie da quell'unione quelle indesiderate, e Pick tiene le chiavi rimanenti. Quindi Omit<User, "password"> è Pick<User, "id" | "name" | "email">. Dato che Pick itera sulle chiavi di T, i modificatori readonly e ? delle proprietà rimanenti vengono mantenuti.
Nota il vincolo: K extends keyof any, non K extends keyof T. Questo dettaglio è l'argomento della prossima sezione.
Omit non controlla che la chiave esista
keyof any è string | number | symbol, quindi Omit accetta qualsiasi chiave, anche una che T non ha. Un errore di battitura compila e non rimuove nulla:
Nessun errore da nessuna parte, e la password finisce nel JSON. Il vincolo permissivo è stato scelto di proposito quando Omit è stato aggiunto in TypeScript 3.5. Le ragioni del team: nel codice generico le chiavi omesse spesso non provengono da T (una funzione che unisce due oggetti può tipizzare il suo argomento rest come Omit<T1, keyof T2>), nemmeno Exclude vincola il suo secondo parametro, e una versione vincolata avrebbe rotto molti dei tipi Omit scritti a mano che i progetti avevano già. Per il codice di tutti i giorni, una versione più rigorosa intercetta l'errore di battitura:
index.ts(10,36): error TS2344: Type '"pasword"' does not satisfy the constraint 'keyof User'.
index.ts(11,7): error TS2741: Property 'password' is missing in type '{ id: number; name: string; }' but required in type 'PublicUser'.
Il primo errore è quello che conta: la chiave viene controllata rispetto a keyof User. Il secondo ne è una conseguenza, dato che non è stato omesso nulla e PublicUser richiede ancora password. Correggi l'ortografia in "password" e compila. Molti codebase lo definiscono una volta in un file di tipi condiviso e lo usano al posto di Omit.
Sovrascrivere il tipo di una proprietà
Un'interfaccia non può estenderne un'altra e cambiare una proprietà in un tipo incompatibile: interface ApiUser extends User { id: string } fallisce con error TS2430: Interface 'ApiUser' incorrectly extends interface 'User'. Prima ometti la proprietà, poi dichiarala di nuovo:
Con un alias di tipo la stessa cosa è un'intersezione: type ApiUser = Omit<User, "id"> & { id: string }. Senza l'Omit, l'intersezione User & { id: string } darebbe a id il tipo number & string, cioè never.
Omit sui tipi unione
Omit non è distributivo. Su un'unione parte dal keyof dell'intera unione, che contiene solo le chiavi condivise da tutti i membri, quindi le proprietà specifiche di ciascun membro spariscono:
index.ts(8,39): error TS2353: Object literal may only specify known properties, and 'radius' does not exist in type 'NewShape'.
NewShape è diventato { kind: "circle" | "square" }. Per omettere da ciascun membro separatamente, applica Omit dentro un tipo condizionale distributivo:
T extends unknown ? ... : never è sempre vero; serve solo a far distribuire il tipo condizionale sull'unione. La pagina sui tipi condizionali spiega perché funziona. Usa DistributiveOmit ogni volta che il tipo da cui ometti è un'unione discriminata.
Omettere una proprietà annidata
Omit guarda solo le chiavi di primo livello, quindi Omit<Settings, "editor.wordWrap"> non rimuove nulla (e, come visto sopra, non si lamenta). Per togliere una proprietà di un livello più in basso, ometti il genitore e aggiungilo di nuovo con un suo Omit:
Settings["editor"] è un indexed access type: dà un nome al tipo dell'oggetto annidato così che Omit possa lavorarci.
Rimuovere proprietà in fase di esecuzione
Omit cambia solo il tipo. Per produrre un oggetto senza quelle proprietà, toglile con la destrutturazione, oppure scrivi un piccolo helper generico il cui tipo di ritorno è Omit:
L'helper vincola K a keyof T, quindi una chiave scritta male è un errore nel punto della chiamata. La copia è superficiale: gli oggetti annidati sono condivisi con l'originale.
Omit vs Pick vs Exclude
| Agisce su | Mantiene | Esempio | Risultato | |
|---|---|---|---|---|
Omit<T, K> | tipi oggetto | tutte le proprietà tranne K | Omit<User, "password"> | { id; name; email } |
Pick<T, K> | tipi oggetto | solo le proprietà in K | Pick<User, "id" | "name"> | { id; name } |
Exclude<U, M> | tipi unione | i membri dell'unione non assegnabili a M | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Scegli tra Omit e Pick in base a cosa deve succedere quando il tipo di origine cresce. Omit include automaticamente le nuove proprietà, il che va bene per "tutto tranne il segreto". Pick non le include mai, il che va bene per "esattamente questi campi" ed è più sicuro per tutto ciò che esce dal tuo server. Pick controlla le sue chiavi rispetto a keyof T; Omit no.
Domande frequenti
Cosa fa Omit in TypeScript?
Omit<T, K> costruisce un nuovo tipo oggetto con tutte le proprietà di T tranne quelle le cui chiavi sono in K. Omit<User, "password"> è User senza password. Cambia solo il tipo; non rimuove nulla da un oggetto in fase di esecuzione.
Come ometto più proprietà in TypeScript?
Passa un'unione di chiavi come secondo argomento: Omit<User, "password" | "createdAt" | "updatedAt">. Vengono rimosse tutte le chiavi dell'unione.
Che differenza c'è tra Omit ed Exclude?
Omit rimuove proprietà da un tipo oggetto: Omit<{ a: 1; b: 2 }, "a"> è { b: 2 }. Exclude rimuove membri da un tipo unione: Exclude<"a" | "b", "a"> è "b". Omit è implementato con Exclude: Pick<T, Exclude<keyof T, K>>.
Perché Omit non dà errore con una chiave che non esiste?
Il suo parametro delle chiavi è vincolato a keyof any (qualsiasi string, number o symbol), non a keyof T, quindi un errore di battitura come Omit<User, "pasword"> compila e non rimuove nulla. Definisci type StrictOmit<T, K extends keyof T> = Omit<T, K> per ottenere un errore con le chiavi sconosciute.
Come rimuovo una proprietà da un oggetto in fase di esecuzione in TypeScript?
Usa la destrutturazione con un elemento rest: const { password, ...rest } = user;. TypeScript tipizza rest come l'oggetto senza password, la stessa forma di Omit<User, "password">, e la proprietà sparisce davvero dal nuovo oggetto.