Menu

Exclude, Extract y NonNullable en TypeScript

Exclude, Extract y NonNullable filtran los miembros de un tipo unión. Aprende qué conserva y qué quita cada uno, cómo elegir miembros de una unión por su forma, cómo se construyen a partir de conditional types y en qué se diferencian de Omit y Pick.

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

Exclude<T, U> quita miembros de un tipo unión, Extract<T, U> conserva solo los miembros que coinciden y NonNullable<T> quita null y undefined. Los tres filtran uniones; no tocan las propiedades de los objetos.

Como todos los utility types, solo existen en tiempo de compilación. El compilador rechaza la última línea; el JavaScript que se ejecuta no tiene ni idea de qué es Visible.

Exclude, Extract y NonNullable de un vistazo

TipoConservaEjemploResultado
Exclude<T, U>los miembros de T que no se pueden asignar a UExclude<"a" | "b" | "c", "a">"b" | "c"
Extract<T, U>los miembros de T que se pueden asignar a UExtract<"a" | "b" | "c", "a" | "z">"a"
NonNullable<T>todo excepto null y undefinedNonNullable<string | null | undefined>string

«Se pueden asignar a U» es la clave. U no tiene por qué ser una lista de miembros exactos; puede ser un tipo más amplio en el que encajen varios miembros:

Elegir miembros de una unión por su forma

Sobre una unión discriminada, Extract con una forma parcial saca las variantes que la tienen. Es la forma habitual de dar nombre a una variante de una unión que no escribiste tú:

La forma { type: "click" } es un supertipo solo de la variante click, así que esa es la que conserva Extract. Una forma que encaja con varias variantes (por ejemplo { x: number } si dos la tuvieran) las conserva todas.

NonNullable

NonNullable<T> es la versión en tipos de una comprobación !== null && !== undefined. Aparece cuando derivas un tipo de algo opcional:

User["manager"] es { name: string } | null | undefined (el ? añade undefined), y NonNullable quita los dos. Desde TypeScript 5.5 el compilador también infiere ese predicado por sí solo, así que raw.filter((s) => s != null) ya tiene tipo string[]; el s is NonNullable<typeof s> explícito deja visible la intención y funciona en versiones anteriores. Su definición es type NonNullable<T> = T & {}: el tipo objeto vacío {} acepta cualquier valor excepto null y undefined, así que intersecar con él quita exactamente esos dos.

Cómo se construyen

Exclude y Extract son conditional types de una línea en la librería estándar:

type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

Cuando T es una unión, un conditional type se aplica a cada miembro por separado (se distribuye), y los miembros never desaparecen del resultado. Así, Exclude<"a" | "b", "a"> se convierte en ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b"), que es never | "b", que es "b". La página de conditional types explica la distribución en detalle.

Exclude frente a Omit, Extract frente a Pick

Los nombres se confunden con facilidad porque las dos parejas «quitan» o «conservan» algo. La diferencia está en aquello sobre lo que operan:

Trabaja sobreQuita o conservaEjemplo
Exclude<T, U>una uniónmiembros de la uniónExclude<"a" | "b", "a"> → "b"
Extract<T, U>una uniónmiembros de la uniónExtract<"a" | "b", "a"> → "a"
Omit<T, K>un tipo objetopropiedadesOmit<User, "password">
Pick<T, K>un tipo objetopropiedadesPick<User, "name">

Usar Exclude sobre un tipo objeto es un despiste habitual, y falla sin avisar:

Account no se puede asignar a "password", así que Exclude lo conserva entero. El propio Omit se construye con Exclude aplicado a las claves: Omit<T, K> es Pick<T, Exclude<keyof T, K>>. Consulta Omit para saber más.

Un Exclude más estricto

El segundo parámetro de Exclude acepta cualquier tipo, así que una errata no quita nada y no da ningún error. Añadir una restricción hace que el compilador compruebe que lo que excluyes está de verdad en la unión:

index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.

Corrige la ortografía y compila, con Strict igual a "published" | "archived". El mismo truco funciona con Extract y con un Omit estricto (K extends keyof T).

Preguntas frecuentes

¿Qué hace Exclude en TypeScript?

Exclude<T, U> quita de la unión T todos los miembros que se pueden asignar a U. Exclude<"a" | "b" | "c", "a"> es "b" | "c". Para quitar varios miembros, pasa una unión como U: Exclude<T, "a" | "b">.

¿Qué diferencia hay entre Exclude y Extract?

Son opuestos. Exclude<T, U> conserva los miembros de T que no se pueden asignar a U; Extract<T, U> conserva solo los que sí. Para los mismos T y U, los dos resultados juntos forman T.

¿Qué diferencia hay entre Exclude y Omit?

Exclude trabaja sobre un tipo unión y quita miembros de la unión. Omit trabaja sobre un tipo objeto y quita propiedades. Exclude<User, "password"> no hace nada útil (devuelve User), mientras que Omit<User, "password"> da el tipo objeto sin esa propiedad. Omit se construye sobre Exclude: excluye la clave de keyof T.

¿Qué hace NonNullable en TypeScript?

NonNullable<T> quita null y undefined de un tipo: NonNullable<string | null | undefined> es string. Se define como T & {}, porque {} acepta cualquier valor excepto null y undefined.

¿Por qué Exclude no da error con un miembro que no existe?

Su segundo parámetro no tiene restricción, así que acepta cualquier tipo, y una errata como Exclude<Status, "drafft"> no quita nada sin avisar. Define una versión más estricta, type StrictExclude<T, U extends T> = Exclude<T, U>, para que el compilador compruebe que U forma parte de T.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR