Concepto clave
En arquitecturas frontend complejas, los patrones genéricos y el tipado profundo transforman TypeScript de una herramienta de verificación estática a un sistema de diseño que previene errores en tiempo de compilación. Imagina construir un puente: los genéricos son los planos parametrizados que se adaptan a diferentes tipos de carga (datos), mientras que el tipado profundo es la inspección estructural que garantiza que cada pieza encaje perfectamente antes de montarla.
En una aplicación de gestión de tareas avanzada, esto significa definir contratos de tipo que modelen el dominio empresarial con precisión. Por ejemplo, una tarea no es solo un objeto con title y completed, sino una entidad con estados transicionales, dependencias y reglas de negocio. Los genéricos permiten crear componentes reutilizables como DataTable<Task> o Form<TaskInput> que se tipan automáticamente según el contexto, reduciendo duplicación y aumentando la seguridad.
"El tipado profundo no es sobre ser estricto, sino sobre capturar la intención del dominio en el código." — Principio de diseño TypeScript avanzado
Cómo funciona en la práctica
Vamos a implementar un sistema de filtros genérico para nuestra aplicación de tareas. Primero, definimos tipos base:
type TaskStatus = 'pending' | 'in-progress' | 'completed' | 'archived';
type Priority = 'low' | 'medium' | 'high';
interface Task {
id: string;
title: string;
description: string;
status: TaskStatus;
priority: Priority;
dueDate: Date;
dependencies: Task[];
}Luego, creamos un filtro genérico que acepte cualquier tipo de dato y permita combinaciones:
type FilterCondition<T> = (item: T) => boolean;
class GenericFilter<T> {
private conditions: FilterCondition<T>[] = [];
addCondition(condition: FilterCondition<T>): this {
this.conditions.push(condition);
return this;
}
apply(items: T[]): T[] {
return items.filter(item => this.conditions.every(cond => cond(item)));
}
}
// Uso con Task
const taskFilter = new GenericFilter<Task>()
.addCondition(task => task.status === 'pending')
.addCondition(task => task.priority === 'high');
const filteredTasks = taskFilter.apply(tasksArray);Este patrón permite construir filtros complejos de forma tipada y reutilizable.
Caso de estudio
Implementemos un gestor de estado para tareas con tipado profundo. Creamos un TaskManager que use genéricos para acciones y reducers:
type Action<T, P = void> = P extends void ? { type: T } : { type: T; payload: P };
type TaskAction = Action<'ADD_TASK', Task> | Action<'UPDATE_TASK', { id: string; updates: Partial<Task> }> | Action<'DELETE_TASK', string>;
class TaskStore {
private tasks: Task[] = [];
dispatch(action: TaskAction): void {
switch (action.type) {
case 'ADD_TASK':
this.tasks.push(action.payload);
break;
case 'UPDATE_TASK':
const index = this.tasks.findIndex(t => t.id === action.payload.id);
if (index !== -1) {
this.tasks[index] = { ...this.tasks[index], ...action.payload.updates };
}
break;
case 'DELETE_TASK':
this.tasks = this.tasks.filter(t => t.id !== action.payload);
break;
}
}
getTasksByStatus(status: TaskStatus): Task[] {
return this.tasks.filter(task => task.status === status);
}
}Este enfoque garantiza que cada acción tenga el payload correcto, evitando errores comunes como pasar un string donde se espera un objeto.
Errores comunes
- Over-engineering con genéricos: Crear genéricos demasiado complejos que dificultan la lectura. Solución: Usa genéricos solo cuando haya verdadera reutilización, y documenta con ejemplos.
- Tipado superficial en uniones: Definir
type Status = stringen lugar detype Status = 'pending' | 'completed'. Esto pierde seguridad. Solución: Siempre usa uniones literales para dominios finitos. - Ignorar inferencia de tipos: Escribir tipos explícitos innecesarios, como
const x: number = 5. Solución: Deja que TypeScript infiera cuando sea obvio, y solo anota en límites de módulo o APIs públicas. - No validar datos en runtime: Asumir que los datos de API coinciden con tus tipos. Solución: Usa librerías como Zod o io-ts para validación en runtime con tipado.
- Abusar de
anyoas: Usarlos para silenciar errores en lugar de corregir el tipo. Solución: Refactoriza para mantener la integridad del tipado, usandounknowny type guards.
Checklist de dominio
- ¿Puedes crear un componente genérico que funcione con al menos tres tipos de datos diferentes sin duplicar código?
- ¿Has implementado un sistema de filtros o búsqueda con tipado estricto que prevenga errores de propiedad?
- ¿Usas uniones discriminadas para modelar estados de dominio, como tareas con transiciones válidas?
- ¿Validas datos en runtime (ej., respuestas de API) y los conviertes a tus tipos TypeScript?
- ¿Evitas el uso de
anyen código de producción, reemplazándolo conunknowny type guards? - ¿Documentas tus genéricos con ejemplos de uso para mejorar la mantenibilidad?
- ¿Aplicas tipado profundo en al menos un módulo complejo, como gestión de estado o formularios?
Implementar un sistema de formularios genérico para tareas
En este ejercicio, crearás un sistema de formularios genérico que pueda manejar diferentes tipos de tareas con validación tipada. Sigue estos pasos:
- Define un tipo
TaskFormque extiendaTaskpero haga opcionales algunos campos comoidydependenciespara creación. - Crea una clase genérica
GenericForm<T>con métodos para agregar campos, validar y enviar. Usa un tipoFieldConfig<T>para configurar cada campo con validadores. - Implementa validadores específicos para tareas, como verificar que
dueDatesea futura y queprioritysea uno de los valores permitidos. - Integra el formulario en un componente de React o Vue usando TypeScript, mostrando errores de tipo en tiempo de compilación.
- Prueba el formulario con dos tipos de tareas: una básica y una subtarea con dependencias, asegurando la reutilización.
- Usa tipos mapeados en TypeScript para crear FieldConfig basado en las claves de T.
- Considera usar uniones discriminadas en los validadores para manejar diferentes tipos de campos.
- Para la integración con React, usa genéricos en props para tipar los datos del formulario.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.