Menu

Intersection types in TypeScript: combinare tipi con &

Un intersection type A & B descrive un valore che è allo stesso tempo A e B, quindi ha tutti i membri di entrambi. Scopri come combinare tipi oggetto con &, perché le proprietà in conflitto diventano never, perché le intersezioni di unioni tengono solo i membri comuni, e quando usare invece extends.

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

Un intersection type, scritto A & B, descrive un valore che è un A e un B allo stesso tempo. Per i tipi oggetto significa che il valore ha ogni proprietà di entrambi. È il modo per combinare tipi esistenti senza riscrivere le proprietà.

Il secondo oggetto omette team, quindi è l'errore di compilazione TS2322, e la riga successiva del messaggio dice Property 'team' is missing ... but required in type 'Employee'. Un valore Staff può essere passato ovunque sia atteso un Person o un Employee.

Combinare tipi oggetto

& funziona con qualsiasi combinazione di alias di tipo, interfacce e tipi oggetto inline, e con i parametri di tipo generici. È in quest'ultimo caso che è difficile da sostituire: una funzione che aggiunge proprietà a qualunque oggetto riceva può dirlo con precisione.

Chi chiama mantiene il tipo esatto di ciò che ha passato (title, words) più le due proprietà aggiunte. Un interface ... extends non può esprimere "qualunque cosa sia T, più queste", perché un'interfaccia non può estendere un parametro di tipo.

Le proprietà in conflitto diventano never

Quando entrambi i lati dichiarano la stessa proprietà, il tipo della proprietà è l'intersezione dei due. Se quei tipi non hanno alcun valore in comune, la proprietà diventa never, e il compilatore non dice nulla finché non provi a creare un valore:

index.ts(7,22): error TS2322: Type 'string' is not assignable to type 'never'.

L'errore punta all'oggetto, non al tipo che l'ha causato, e questo rende lenti da rintracciare questi bug. Passando il mouse su r.id nell'editor vedi il suo tipo: never. Quando la proprietà in conflitto è un tag letterale, come in type Shape = { kind: "circle" } & { kind: "square" }, TypeScript va oltre e riduce l'intera intersezione a never. Leggere una proprietà di un valore del genere spiega poi il perché: Property 'kind' does not exist on type 'never'. The intersection 'Shape' was reduced to 'never' because property 'kind' has conflicting types in some constituents.

interface ... extends intercetta lo stesso conflitto già nella dichiarazione, con l'errore TS2430. È questa la principale differenza pratica tra i due; la pagina interface vs type li mette a confronto.

Le sovrapposizioni compatibili restringono la proprietà

Se i due tipi della proprietà si sovrappongono, il risultato è la sovrapposizione. È utile, non un errore:

La seconda metà mostra cosa fa & alle unioni: tiene i membri che entrambi i lati hanno in comune. Pensare ai tipi come insiemi di valori lo rende prevedibile. A | B è l'unione dei due insiemi, A & B è la loro sovrapposizione, e una sovrapposizione vuota è never.

Intersezione vs unione

I nomi vengono dalla teoria degli insiemi, e sembrano al contrario quando li applichi alle proprietà degli oggetti:

A | B (unione)A & B (intersezione)
Un valore èun A o un Bun A e un B
Insieme dei valori ammessipiù grandepiù piccolo
Proprietà che puoi usaresolo quelle presenti in entrambitutte, da ciascuno
string con numberstring | numbernever
"a" | "b" con "b" | "c""a" | "b" | "c""b"

Un'intersezione di tipi oggetto ha più proprietà proprio perché ammette meno valori: solo gli oggetti che hanno tutto.

Intersezione vs extends

type C = A & Binterface C extends A, B
Funziona conqualsiasi tipo, comprese unioni e parametri di tipotipi oggetto con membri noti staticamente
Proprietà in conflittodiventa never in silenzioerrore TS2430 o TS2320 nella dichiarazione
Risultatoun'intersezione, controllata componente per componenteun unico tipo piatto con nome, le cui relazioni vengono messe in cache
Composizioni grandipossono rallentare il controllo dei tipipreferito dalla pagina Performance della wiki di TypeScript

Per combinare pochi alias di tipi oggetto, & è idiomatico e va benissimo. Per un tipo costruito da molte parti, o un tipo di un'API pubblica, extends dà errori prima e controlli dei tipi più leggeri.

Intersezioni con i primitivi: branding

Intersecare un primitivo con un tipo oggetto non produce never: string & { readonly __brand: "UserId" } è una stringa che porta un marcatore in più, valido solo in fase di compilazione. Nessuna stringa reale ha quella proprietà, ed è proprio questo il punto: solo il codice che afferma deliberatamente il brand può crearne una, quindi una semplice string o un OrderId non si possono più passare dove è atteso un UserId. Questa tecnica ha una pagina tutta sua, branded types.

Domande frequenti

Cos'è un intersection type in TypeScript?

Un tipo scritto A & B i cui valori devono soddisfare sia A sia B allo stesso tempo. Per i tipi oggetto, significa che il valore ha ogni proprietà di A e ogni proprietà di B. È il modo abituale di combinare due alias di tipo in uno.

Qual è la differenza tra un'unione e un'intersezione?

Un'unione A | B significa "uno dei due": il valore può essere un A o un B, e puoi usare solo ciò che hanno in comune finché non restringi il tipo. Un'intersezione A & B significa "entrambi": il valore ha tutto di tutti e due. Con i tipi oggetto, l'unione accetta più valori e l'intersezione ha più proprietà.

Perché il mio intersection type è never?

Perché nessun valore può soddisfare entrambi i lati. string & number è never, e { id: string } & { id: number } rende id un string & number, quindi la proprietà è never e non si può creare nessun oggetto. Se due tipi oggetto hanno lo stesso tag letterale con valori diversi (kind: "circle" e kind: "square"), l'intera intersezione si riduce a never.

Conviene usare un'intersezione o extends?

Entrambi combinano tipi oggetto. interface X extends A, B segnala le proprietà in conflitto nella dichiarazione ed è consigliato dal team di TypeScript per comporre grandi tipi oggetto. & funziona con qualsiasi tipo, comprese unioni e parametri generici, che extends non può combinare. Usa & per gli alias di tipo e gli helper generici, ed extends quando costruisci interfacce.

Come si uniscono due tipi oggetto in TypeScript?

Scrivi type Merged = A & B. Per il valore a runtime, fai lo spread di entrambi gli oggetti: const merged: A & B = { ...a, ...b }. Se A e B condividono una proprietà con tipi diversi, il tipo di quella proprietà diventa never; usa Omit<A, keyof B> & B quando le proprietà del secondo oggetto devono sostituire quelle del primo.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA