ProgramaciónDesarrollador Fullstack

¿Cómo funciona Exclude en TypeScript, cuándo usarlo para manipulaciones con tipos union y qué matices existen al trabajar con este tipo utilitario?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Exclude<T, U> es un tipo utilitario que apareció en TypeScript para restar un tipo de otro, cuando se requiere excluir algunos valores de un tipo union.

Historia de la cuestión

Originalmente, no había una forma conveniente en TypeScript de restar un tipo de otro. Al crear API genéricas, así como al realizar refactorizaciones, a menudo era necesario obtener un tipo "residual": todo, excepto los valores prohibidos. En lugar de realizar manipulaciones manuales con union, era necesario mantener varias interfaces similares.

Problema

Por ejemplo, cuando hay un tipo 'A | B | C', pero se necesita obtener un tipo sin B. Esto es a menudo requerido al construir parámetros de entrada complejos para funciones, filtrar valores permitidos y formar dinámicamente tipos.

Solución

Exclude resuelve este problema. Su firma simplificada es la siguiente:

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

Devuelve el tipo que excluye de T todos los miembros de U.

Ejemplo:

type Status = 'draft' | 'published' | 'removed'; type UserVisibleStatus = Exclude<Status, 'removed'>; const visible: UserVisibleStatus = 'draft'; // OK

Características clave:

  • Permite formar tipos dinámicos mediante la "substracción" de partes de un union.
  • Simplifica el refactorizado: al cambiar el tipo base, todos los derivados se actualizan automáticamente.
  • Puede ser usado, por ejemplo, para filtrar casos de switch o claves de objeto.

Preguntas engañosas.

¿Se puede usar Exclude para tipos normales, y no para unions?

Si T no es un tipo union, pero está dentro de U, Exclude funcionará igualmente, pero el resultado puede ser never o T, lo que no siempre es intuitivo.

Exclude<'a', 'a'> // resultado: never Exclude<'a', 'b'> // resultado: 'a'

¿Elimina Exclude todas las menciones de tipo en la estructura del objeto?

No, Exclude no pasa recursivamente por los campos anidados del tipo, solo excluye en el nivel superior del union.

¿Cómo funciona Exclude con interfaces y tipos objeto?

Compara todo el tipo, no propiedades individuales. Por lo tanto, Exclude de un union de varias interfaces elimina únicamente aquellas que coinciden completamente con U.

interface A { x: number }; interface B { y: string }; // Exclude<A|B, B> da: A (B coincide completamente)

Errores comunes y anti-patrones

  • Intentos de aplicar Exclude para coincidencias anidadas o parciales.
  • Uso para "eliminar" propiedades de una interfaz, y no variantes de union.
  • Ignorar la posibilidad de obtener el tipo never cuando los tipos coinciden completamente.

Ejemplo de la vida real

Caso negativo

Validación de roles de usuario a través de Exclude<UserRoles, 'admin'>, pero se olvidó que Exclude no se aplica a estructuras anidadas: los derechos 'admin:sub' no fueron excluidos.

Pros:

  • Simplicidad en la formación del tipo de roles.

Contras:

  • Comportamiento no evidente con tipos anidados o similares; se omitió un rol crítico.

Caso positivo

Uso de Exclude para restringir el API público de acciones: Exclude<Action, 'delete'>, lo que excluye la operación peligrosa.

Pros:

  • Seguridad a nivel de tipado, no se puede invocar una acción prohibida.

Contras:

  • Necesidad de mantener listas actualizadas de tipos base.