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
| Tipo | Conserva | Ejemplo | Resultado |
|---|---|---|---|
Exclude<T, U> | los miembros de T que no se pueden asignar a U | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Extract<T, U> | los miembros de T que se pueden asignar a U | Extract<"a" | "b" | "c", "a" | "z"> | "a" |
NonNullable<T> | todo excepto null y undefined | NonNullable<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 sobre | Quita o conserva | Ejemplo | |
|---|---|---|---|
Exclude<T, U> | una unión | miembros de la unión | Exclude<"a" | "b", "a"> → "b" |
Extract<T, U> | una unión | miembros de la unión | Extract<"a" | "b", "a"> → "a" |
Omit<T, K> | un tipo objeto | propiedades | Omit<User, "password"> |
Pick<T, K> | un tipo objeto | propiedades | Pick<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.