TypeScript Avanzado: Patrones Genéricos y Tipado Profundo para Arquitecturas Complejas

Tipos Condicionales y Mapeados en Acción

Concepto claveLos tipos condicionales y tipos mapeados son herramientas avanzadas de TypeScript que permiten crear tipos dinámicos basados en otros tipos. Piensa en ellos como "funciones" para tipos: mientras que las funciones transforman valores en tiempo de ejecución, estos tipos transforman tipos en tiempo de compilación.Los tipos condicionales utilizan la sintaxis T extends U ? X : Y para crear tipos que dependen de una condición. Son como sentencias if/else para el sistema de tipos. Los tip
Tiempo de estudio
15 Min

Concepto clave

Los tipos condicionales y tipos mapeados son herramientas avanzadas de TypeScript que permiten crear tipos dinámicos basados en otros tipos. Piensa en ellos como "funciones" para tipos: mientras que las funciones transforman valores en tiempo de ejecución, estos tipos transforman tipos en tiempo de compilación.

Los tipos condicionales utilizan la sintaxis T extends U ? X : Y para crear tipos que dependen de una condición. Son como sentencias if/else para el sistema de tipos. Los tipos mapeados, por otro lado, transforman cada propiedad de un tipo existente usando la sintaxis {[K in keyof T]: ...}. Imagina que tienes un tipo que representa un objeto de configuración y necesitas crear una versión donde todas las propiedades sean opcionales o de solo lectura: los tipos mapeados son tu herramienta.

"Los tipos condicionales y mapeados son la base para construir utilidades de tipo reutilizables y arquitecturas de tipo flexibles en proyectos complejos."

Cómo funciona en la práctica

Veamos un ejemplo paso a paso de cómo combinar ambos conceptos. Supongamos que queremos crear un tipo que extraiga solo las propiedades de un objeto que sean funciones.

type FunctionProperties = {
[K in keyof T]: T[K] extends Function ? K : never
}[keyof T];

type ExtractFunctions = Pick>;

// Ejemplo de uso
interface UserActions {
id: number;
name: string;
save: () => void;
delete: (id: number) => boolean;
update: (data: Partial) => void;
}

type UserFunctions = ExtractFunctions;
// Resultado: { save: () => void; delete: (id: number) => boolean; update: (data: Partial) => void }

Paso 1: FunctionProperties crea un tipo mapeado donde cada propiedad se evalúa con un tipo condicional. Si la propiedad es una función, devuelve el nombre de la propiedad (K), si no, devuelve never.

Paso 2: Al indexar con [keyof T], obtenemos una unión de solo los nombres de propiedades que son funciones.

Paso 3: ExtractFunctions usa Pick para crear un nuevo tipo con solo esas propiedades.

Caso de estudio

Imagina que estás construyendo una librería de validación para formularios. Necesitas tipos que automaticen la creación de interfaces para errores de validación basadas en los esquemas de formulario.

// Definimos un esquema de formulario
type FormSchema = {
email: string;
password: string;
age: number;
terms: boolean;
};

// Creamos un tipo que mapea cada propiedad a su tipo de error
type ValidationErrors = {
[K in keyof T]?: string | null;
};

// Tipo condicional para determinar si un formulario es válido
type IsValid = ValidationErrors extends { [K in keyof T]?: null }
? true
: false;

// Función que utiliza estos tipos
function validateForm(data: T, errors: ValidationErrors): IsValid {
const hasErrors = Object.values(errors).some(error => error !== null);
return !hasErrors as IsValid;
}

// Uso en código
const formData: FormSchema = {
email: "[email protected]",
password: "secret123",
age: 25,
terms: true
};

const errors: ValidationErrors = {
email: null, // sin error
password: "Debe tener al menos 8 caracteres",
age: null,
terms: null
};

const isValid = validateForm(formData, errors); // Tipo: false

Este caso muestra cómo los tipos mapeados (ValidationErrors) y condicionales (IsValid) trabajan juntos para crear un sistema de tipos seguro para validación. La tabla siguiente resume las transformaciones:

Tipo OriginalTipo TransformadoTransformación Aplicada
FormSchemaValidationErrorsCada propiedad se hace opcional y cambia a string | null
ValidationErrorsIsValidEvalúa si todas las propiedades son null (true) o no (false)

Errores comunes

  • Confundir extends con asignación: En tipos condicionales, T extends U verifica si T es asignable a U, no si son iguales. Para verificar igualdad exacta, usa [T] extends [U] ? ... : ....
  • Olvidar el operador keyof en tipos mapeados: Siempre usa [K in keyof T] para iterar sobre propiedades. [K in T] no funciona porque T no es una unión de keys.
  • No manejar never adecuadamente: Cuando un tipo condicional devuelve never en un tipo mapeado, esa propiedad se excluye del tipo resultante. Úsalo intencionalmente para filtrar propiedades.
  • Abusar de la complejidad: Los tipos condicionales anidados pueden volverse ilegibles rápidamente. Extrae lógica compleja en tipos auxiliares con nombres descriptivos.
  • Ignorar distributividad: Los tipos condicionales sobre uniones son distributivos por defecto. Si necesitas tratar la unión como un todo, envuélvela en un array: [T] extends [U] ? ... : ....

Checklist de dominio

  1. Puedo explicar la diferencia entre T extends U y [T] extends [U] en tipos condicionales
  2. Sé crear un tipo mapeado que transforme todas las propiedades de un tipo (ej: hacerlas opcionales, de solo lectura, o cambiar sus tipos)
  3. Puedo combinar tipos condicionales y mapeados para filtrar propiedades basado en condiciones
  4. Entiendo cómo usar never en tipos mapeados para excluir propiedades no deseadas
  5. Puedo crear tipos utilitarios reutilizables usando estos conceptos (ej: Nullable, PartialBy)
  6. Sé identificar y corregir errores de distributividad en tipos condicionales
  7. Puedo aplicar estos patrones en escenarios reales como validación, normalización de datos, o APIs tipo-safe

Implementa un sistema de permisos tipo-safe


Crea un sistema de permisos para una aplicación donde diferentes roles de usuario tienen acceso a distintas funcionalidades. Sigue estos pasos:

  1. Define un tipo Role con los valores: 'admin', 'editor', 'viewer'.
  2. Crea un tipo Permission que represente las acciones posibles: 'create', 'read', 'update', 'delete'.
  3. Implementa un tipo mapeado RolePermissions que asocie cada rol con un conjunto de permisos (usa un tipo de unión). Por ejemplo: admin tiene todos los permisos, editor tiene ['create', 'read', 'update'], viewer solo ['read'].
  4. Crea un tipo condicional HasPermission que verifique si un rol específico tiene un permiso determinado. Debe devolver true o false como tipo literal.
  5. Implementa una función checkPermission que acepte un rol y un permiso, y devuelva un booleano tipado con HasPermission.
  6. Agrega validación para que si se pasa un permiso no válido para un rol, TypeScript muestre un error en tiempo de compilación.

Entrega el código completo con ejemplos de uso que demuestren tanto casos válidos como inválidos.


Pistas
  • Usa un tipo de unión para los permisos de cada rol, ej: type AdminPermissions = Permission[]
  • Para el tipo condicional, necesitaras verificar si el permiso esta incluido en la union de permisos del rol
  • Considera usar tipos auxiliares para hacer el codigo mas legible

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Texto Leccion 1/20
Estas viendo
Tipos Condicionales y Mapeados en Acción
Hablar por WhatsAppContactar por WhatsApp