Crear Utility Types Personalizados

Lectura
20 min~4 min lectura

Concepto clave

Los Utility Types Personalizados son tipos genéricos que construyes para resolver problemas específicos en tu arquitectura, extendiendo las capacidades nativas de TypeScript. Piensa en ellos como herramientas de taller que diseñas para tareas repetitivas: así como un carpintero crea plantillas para cortes precisos, tú creas tipos que automatizan patrones de tipado complejos.

En arquitecturas avanzadas, estos tipos reducen la redundancia y mejoran la mantenibilidad. Por ejemplo, en una librería de formularios, podrías crear un tipo que extraiga automáticamente las claves de configuración de un objeto, evitando errores manuales. La clave está en combinar tipos condicionales (extends), mapeados (keyof, in) y operadores como infer para crear abstracciones poderosas.

Cómo funciona en la práctica

Vamos a construir un utility type paso a paso. Imagina que necesitas un tipo que convierta todas las propiedades opcionales de un objeto en requeridas, pero solo para un subconjunto de claves. Esto es útil en validaciones de configuración.

type MakeRequired<T, K extends keyof T> = T & {
  [P in K]-?: T[P];
};

interface Config {
  apiUrl?: string;
  timeout?: number;
  retries?: number;
}

type StrictConfig = MakeRequired<Config, 'apiUrl' | 'timeout'>;
// StrictConfig ahora requiere apiUrl y timeout, pero retries sigue opcional

Explicación: K extends keyof T restringe las claves a las existentes en T. [P in K]-? mapea cada clave en K y remueve el ? (opcional). La intersección (&) combina con el tipo original.

Caso de estudio

En una librería de UI, necesitas un tipo que genere variantes de componentes basado en un objeto de configuración. Supongamos un botón con props como size y variant.

type ButtonConfig = {
  size: ['small', 'medium', 'large'];
  variant: ['primary', 'secondary', 'outline'];
};

type GenerateProps<T> = {
  [K in keyof T]: T[K] extends readonly (infer U)[] ? U : never;
};

type ButtonProps = GenerateProps<ButtonConfig>;
// ButtonProps es { size: 'small' | 'medium' | 'large'; variant: 'primary' | 'secondary' | 'outline' }

Este tipo automatiza la creación de uniones a partir de arrays, útil para sistemas de diseño. En la práctica, podrías usarlo para generar tipos para docenas de componentes consistentemente.

Errores comunes

  • Abusar de any en tipos recursivos: En tipos que se auto-referencian, usar any rompe la seguridad. En su lugar, usa condiciones bien definidas con extends.
  • Ignorar distributividad en uniones: Los tipos condicionales se distribuyen sobre uniones. Si no lo controlas, puedes obtener resultados inesperados. Usa [T] extends [U] para evitar distribución cuando no la necesitas.
  • No probar casos límite: Un utility type debe funcionar con tipos vacíos, never, o uniones complejas. Prueba con {}, never, y combinaciones para asegurar robustez.
  • Crear tipos demasiado específicos: Un utility type debe ser reutilizable. Evita acoplarlo a una estructura de datos concreta; en su lugar, hazlo genérico con parámetros flexibles.

Checklist de dominio

  1. Puedo explicar la diferencia entre tipos mapeados, condicionales y genéricos básicos.
  2. He creado al menos 3 utility types que resuelven problemas reales en mi código.
  3. Sé cómo usar infer para extraer tipos dentro de estructuras anidadas.
  4. Puedo depurar errores de tipo en utilities complejos usando herramientas como el inspector de tipos.
  5. Entiendo cuándo un utility type debe ser distribuido o no sobre uniones.
  6. He integrado utilities personalizados en una librería o proyecto con múltiples desarrolladores.
  7. Puedo documentar mis utilities con ejemplos de uso y casos límite.

Construye un Utility Type para Validación de Formularios

En este ejercicio, crearás un utility type que valide un esquema de formulario dinámico. Sigue estos pasos:

  1. Define una interfaz FormSchema que represente un formulario con campos. Cada campo debe tener una propiedad type (ej., 'string', 'number', 'boolean') y required (booleano).
  2. Crea un tipo GenerateFormValues<T extends FormSchema> que genere un tipo para los valores del formulario. Las propiedades deben ser opcionales si required es false, y el tipo debe mapear según type (ej., 'string' a string).
  3. Implementa un tipo ValidateForm<T extends FormSchema, V> que tome el esquema T y un objeto de valores V, y devuelva un tipo que indique errores de validación. Debe marcar campos faltantes o con tipo incorrecto.
  4. Prueba tus tipos con al menos dos esquemas diferentes y valores de ejemplo, verificando en tu editor que los errores se detecten.

Ejemplo de esquema para empezar:

interface MyForm extends FormSchema {
  name: { type: 'string'; required: true };
  age: { type: 'number'; required: false };
}
Pistas
  • Usa tipos mapeados con condiciones para transformar cada campo del esquema.
  • Considera usar un tipo auxiliar para mapear 'string' | 'number' | 'boolean' a los tipos TypeScript correspondientes.
  • Para la validación, puedes devolver un tipo que liste las claves con errores, usando never para los válidos.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.