Exclude<T, U> rimuove membri da un tipo union, Extract<T, U> tiene solo i membri corrispondenti e NonNullable<T> rimuove null e undefined. Tutti e tre filtrano union; non toccano le proprietà degli oggetti.
Come ogni tipo di utilità, esistono solo in compilazione. L'ultima riga viene rifiutata dal compilatore; il JavaScript che viene eseguito non ha idea di cosa sia Visible.
Exclude, Extract e NonNullable in breve
| Tipo | Tiene | Esempio | Risultato |
|---|---|---|---|
Exclude<T, U> | i membri di T non assegnabili a U | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Extract<T, U> | i membri di T assegnabili a U | Extract<"a" | "b" | "c", "a" | "z"> | "a" |
NonNullable<T> | tutto tranne null e undefined | NonNullable<string | null | undefined> | string |
"Assegnabile a U" è l'espressione chiave. U non deve essere per forza un elenco di membri esatti; può essere un tipo più largo in cui rientrano diversi membri:
Scegliere i membri di una union per forma
Su una discriminated union, Extract con una forma parziale estrae le varianti che la possiedono. È il modo abituale per dare un nome a una variante di una union che non hai scritto tu:
La forma { type: "click" } è un supertipo solo della variante click, quindi è quella che Extract tiene. Una forma che corrisponde a più varianti (per esempio { x: number } se due la avessero) le tiene tutte.
NonNullable
NonNullable<T> è la versione a livello di tipi di un controllo !== null && !== undefined. Compare quando ricavi un tipo da qualcosa di facoltativo:
User["manager"] è { name: string } | null | undefined (il ? aggiunge undefined), e NonNullable li toglie entrambi. Da TypeScript 5.5 il compilatore deduce da solo anche quel predicato, quindi raw.filter((s) => s != null) è già tipizzato string[]; l'esplicito s is NonNullable<typeof s> lascia visibile l'intenzione e funziona nelle versioni più vecchie. La sua definizione è type NonNullable<T> = T & {}: il tipo oggetto vuoto {} accetta ogni valore tranne null e undefined, quindi intersecarlo rimuove esattamente quelli.
Come sono costruiti
Exclude ed Extract sono tipi condizionali di una riga nella libreria standard:
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
Quando T è una union, un tipo condizionale viene applicato a ogni membro separatamente (si distribuisce), e i membri never spariscono dal risultato. Quindi Exclude<"a" | "b", "a"> diventa ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b"), cioè never | "b", cioè "b". La pagina sui tipi condizionali tratta la distribuzione nel dettaglio.
Exclude vs Omit, Extract vs Pick
I nomi si confondono facilmente perché entrambe le coppie "rimuovono" o "tengono" qualcosa. La differenza sta in ciò su cui lavorano:
| Lavora su | Rimuove o tiene | Esempio | |
|---|---|---|---|
Exclude<T, U> | una union | membri della union | Exclude<"a" | "b", "a"> → "b" |
Extract<T, U> | una union | membri della union | Extract<"a" | "b", "a"> → "a" |
Omit<T, K> | un tipo oggetto | proprietà | Omit<User, "password"> |
Pick<T, K> | un tipo oggetto | proprietà | Pick<User, "name"> |
Usare Exclude su un tipo oggetto è una svista comune, e fallisce in silenzio:
Account non è assegnabile a "password", quindi Exclude lo tiene intero. Omit stesso è costruito con Exclude applicato alle chiavi: Omit<T, K> è Pick<T, Exclude<keyof T, K>>. Vedi Omit per saperne di più.
Un Exclude più rigoroso
Il secondo parametro di Exclude accetta qualsiasi tipo, quindi un errore di battitura non rimuove nulla e non solleva errori. Aggiungere un vincolo fa verificare al compilatore che ciò che escludi sia davvero nella union:
index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.
Correggi l'ortografia e compila, con Strict uguale a "published" | "archived". Lo stesso trucco funziona per Extract e per un Omit rigoroso (K extends keyof T).
Domande frequenti
Cosa fa Exclude in TypeScript?
Exclude<T, U> rimuove dalla union T ogni membro assegnabile a U. Exclude<"a" | "b" | "c", "a"> è "b" | "c". Per rimuovere più membri, passa una union come U: Exclude<T, "a" | "b">.
Che differenza c'è tra Exclude ed Extract?
Sono opposti. Exclude<T, U> tiene i membri di T che non sono assegnabili a U; Extract<T, U> tiene solo quelli che lo sono. Per gli stessi T e U, i due risultati insieme formano T.
Che differenza c'è tra Exclude e Omit?
Exclude lavora su un tipo union e rimuove membri della union. Omit lavora su un tipo oggetto e rimuove proprietà. Exclude<User, "password"> non fa nulla di utile (restituisce User), mentre Omit<User, "password"> dà il tipo oggetto senza quella proprietà. Omit è costruito su Exclude: esclude la chiave da keyof T.
Cosa fa NonNullable in TypeScript?
NonNullable<T> rimuove null e undefined da un tipo: NonNullable<string | null | undefined> è string. È definito come T & {}, perché {} accetta ogni valore tranne null e undefined.
Perché Exclude non segnala un errore per un membro che non esiste?
Il suo secondo parametro non ha vincoli, quindi accetta qualsiasi tipo, e un errore di battitura come Exclude<Status, "drafft"> non rimuove nulla in silenzio. Definisci una versione più rigorosa, type StrictExclude<T, U extends T> = Exclude<T, U>, per far verificare al compilatore che U faccia parte di T.