Saltar al contenido

Lección 14 de 20 · Lectura y práctica

Crear Utility Types Personalizados

Concepto claveLos 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.

Del concepto a la práctica

Taller de esta lección

Un utility type propio es valioso cuando nombra una regla repetida. RequiredKeys<T, K> puede hacer obligatorias solo ciertas claves y dejar las demás como estaban.

Ejemplo resuelto

ts
type RequiredKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
type Draft = { title?: string; note?: string; done?: boolean };
type Saved = RequiredKeys<Draft, "title" | "done">;
const item: Saved = { title: "Leer", done: false };

El modificador -? elimina la opcionalidad únicamente en las claves elegidas.

Pruébalo

  1. Declara un Saved con title y done.

  2. Omite note, que debe seguir siendo válido.

  3. Omite done y compila.

  4. Observa el diagnóstico.

Comprueba tu resultado

El objeto sin note es correcto; el objeto sin done debe ser rechazado.

Error frecuente: mapear todas las claves con -?, haciendo obligatorio también note. Corrígelo restringiendo K extends keyof T; el utility debe transformar solo la selección solicitada.

Required vuelve obligatoria la presencia de la propiedad; no es una validación de texto no vacío ni una regla de dominio. Con exactOptionalPropertyTypes y un campo que declare explícitamente string|undefined, una propiedad requerida aún puede admitir undefined según su tipo. Si necesitas un valor definido diseña otra utilidad con Exclude<T[P],undefined> y documenta su intención. No conviertas un Draft recibido por JSON en Saved mediante as: valida los valores y construye el modelo.

Diagrama de decisión con ramas Sí y No y un bucle que procesa cada elemento de una lista.
CursaloMapa de práctica
Cómo avanza un programa

Una condición decide qué camino ejecuta el programa. Un bucle repite una operación mientras queden elementos, y termina cuando la lista se agota. Separar ambas ideas ayuda a seguir el flujo y comprobar casos límite.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar

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.

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

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.