Menu

Tipos intersección en TypeScript: combinar tipos con &

Un tipo intersección A & B describe un valor que es A y B a la vez, así que tiene todos los miembros de los dos. Aprende a combinar tipos objeto con &, por qué las propiedades en conflicto se vuelven never, cómo las intersecciones de uniones solo conservan los miembros compartidos y cuándo usar extends.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un tipo intersección, escrito A & B, describe un valor que es un A y un B al mismo tiempo. Con tipos objeto, eso significa que el valor tiene todas las propiedades de los dos. Es la forma de combinar tipos existentes sin volver a escribir las propiedades.

Al segundo objeto le falta team, así que es el error de compilación TS2322, y la siguiente línea del mensaje dice Property 'team' is missing ... but required in type 'Employee'. Un valor Staff se puede pasar en cualquier sitio donde se espera un Person o un Employee.

Combinar tipos objeto

& funciona con cualquier mezcla de alias de tipo, interfaces y tipos objeto en línea, y con parámetros de tipo genéricos. Este último caso es donde es difícil de sustituir: una función que añade propiedades a cualquier objeto que recibe puede decirlo con precisión.

Quien llama conserva el tipo exacto de lo que pasó (title, words) más las dos propiedades añadidas. Un interface ... extends no puede expresar «sea lo que sea T, más esto», porque una interfaz no puede extender un parámetro de tipo.

Las propiedades en conflicto se vuelven never

Cuando los dos lados declaran la misma propiedad, el tipo de la propiedad es la intersección de los dos. Si esos tipos no tienen ningún valor en común, la propiedad pasa a ser never, y el compilador no dice nada hasta que intentas crear un valor:

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

El error señala el objeto, no el tipo que lo causó, lo que hace que estos bugs sean lentos de rastrear. Pasar el cursor por r.id en el editor muestra su tipo: never. Cuando la propiedad en conflicto es una etiqueta literal, como en type Shape = { kind: "circle" } & { kind: "square" }, TypeScript va más allá y reduce toda la intersección a never. Leer una propiedad de un valor así explica entonces por qué: 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 detecta el mismo conflicto en la declaración, con el error TS2430. Es la principal diferencia práctica entre los dos; la página de interface frente a type los compara lado a lado.

Los solapamientos compatibles estrechan la propiedad

Si los dos tipos de la propiedad sí se solapan, el resultado es el solapamiento. Eso es útil, no un error:

La segunda mitad muestra lo que hace & con las uniones: conserva los miembros que tienen en común los dos lados. Pensar en los tipos como conjuntos de valores lo hace predecible. A | B es la unión de los dos conjuntos, A & B es su solapamiento, y un solapamiento vacío es never.

Intersección frente a unión

Los nombres vienen de la teoría de conjuntos, y parecen al revés cuando se aplican a las propiedades de los objetos:

A | B (unión)A & B (intersección)
Un valor esun A o un Bun A y un B
Conjunto de valores permitidosmás grandemás pequeño
Propiedades que puedes usarsolo las que están en los dostodas las de cualquiera
string con numberstring | numbernever
"a" | "b" con "b" | "c""a" | "b" | "c""b"

Una intersección de tipos objeto tiene más propiedades justamente porque admite menos valores: solo los objetos que lo tienen todo.

Intersección frente a extends

type C = A & Binterface C extends A, B
Funciona concualquier tipo, incluidas uniones y parámetros de tipotipos objeto con miembros conocidos estáticamente
Propiedad en conflictopasa a ser never sin avisarerror TS2430 o TS2320 en la declaración
Resultadouna intersección, comprobada componente a componenteun único tipo plano con nombre cuyas relaciones se guardan en caché
Composiciones grandespueden ralentizar la comprobación de tiposlo prefiere la página Performance de la wiki de TypeScript

Para combinar unos pocos alias de tipos objeto, & es idiomático y está bien. Para un tipo construido a partir de muchas partes, o para el tipo de una API pública, extends da errores antes y comprobaciones de tipos más baratas.

Intersecciones con primitivos: branding

Intersecar un primitivo con un tipo objeto no produce never: string & { readonly __brand: "UserId" } es un string que lleva una marca extra que solo existe en tiempo de compilación. Ningún string real tiene esa propiedad, y esa es precisamente la idea: solo el código que afirma la marca a propósito puede crear uno, así que un string normal o un OrderId ya no se pueden pasar donde se espera un UserId. Esa técnica tiene su propia página, branded types.

Preguntas frecuentes

¿Qué es un tipo intersección en TypeScript?

Un tipo escrito A & B cuyos valores deben cumplir A y B al mismo tiempo. Con tipos objeto, eso significa que el valor tiene todas las propiedades de A y todas las de B. Es la forma habitual de combinar dos alias de tipo en uno.

¿Qué diferencia hay entre una unión y una intersección?

Una unión A | B significa "uno de los dos": el valor puede ser un A o un B, y solo puedes usar lo que comparten hasta que lo estreches. Una intersección A & B significa "los dos": el valor tiene todo lo de ambos. Con tipos objeto, la unión acepta más valores y la intersección tiene más propiedades.

¿Por qué mi tipo intersección es never?

Porque ningún valor puede cumplir los dos lados. string & number es never, y { id: string } & { id: number } hace que id sea un string & number, así que la propiedad es never y no se puede crear ningún objeto. Si dos tipos objeto tienen la misma etiqueta literal con valores distintos (kind: "circle" y kind: "square"), toda la intersección se reduce a never.

¿Uso una intersección o extends?

Los dos combinan tipos objeto. interface X extends A, B informa de las propiedades en conflicto en la declaración y es lo que recomienda el equipo de TypeScript para componer tipos objeto grandes. & funciona con cualquier tipo, incluidas uniones y parámetros genéricos, que extends no puede combinar. Usa & para alias de tipo y funciones auxiliares genéricas, y extends al construir interfaces.

¿Cómo fusiono dos tipos objeto en TypeScript?

Escribe type Merged = A & B. Para el valor en tiempo de ejecución, expande los dos objetos: const merged: A & B = { ...a, ...b }. Si A y B comparten una propiedad con tipos distintos, el tipo de esa propiedad pasa a ser never; usa Omit<A, keyof B> & B cuando las propiedades del segundo objeto deban sustituir a las del primero.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR