Saltar al contenido

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

Planificar una Aplicación con Tipado desde Cero

Concepto clavePlanificar una aplicación con tipado desde cero significa diseñar primero los tipos antes de escribir código, estableciendo contratos claros entre componentes. Imagina construir un puente: primero defines los planos.

Del concepto a la práctica

Taller de esta lección

Planificar tipos antes de componentes ayuda a fijar las fronteras del gestor de tareas. Conviene separar la entidad persistida, el formulario de alta y los eventos, porque cada uno tiene campos distintos.

Ejemplo resuelto

ts
type Task = { id: string; title: string; priority: "low" | "high"; done: boolean };
type NewTask = Omit<Task, "id" | "done">;
type TaskEvent = { type: "created"; task: Task } | { type: "completed"; id: string };

Una alta no puede declarar done por accidente; el estado inicial se decide en una función.

Pruébalo

  1. Crea una NewTask con título y prioridad.

  2. Construye una Task asignando id y done: false.

  3. Emite un evento completed.

  4. Intenta incluir task en ese evento.

Comprueba tu resultado

La tarea final debe contener cuatro campos y el evento de finalización solo type e id.

Error frecuente: reutilizar Task para el formulario y hacer todos sus campos opcionales. Corrígelo con Omit; así cada frontera expresa su responsabilidad.

Omit y las comprobaciones de exceso de propiedades de un literal no impiden que una variable con más campos sea asignable estructuralmente. Una NewTask externa necesita validación de title y priority y selección de campos; la función de alta debe generar el id y fijar donefalse sin copiar done del formulario. Define además unicidad del id y la transición cuando ya estaba completada: esos contratos de ejecución no aparecen automáticamente por declarar TaskEvent.

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

Planificar una aplicación con tipado desde cero significa diseñar primero los tipos antes de escribir código, estableciendo contratos claros entre componentes. Imagina construir un puente: primero defines los planos estructurales (tipos), luego colocas los materiales (implementación). En TypeScript avanzado, esto implica modelar el dominio completo con interfaces, tipos genéricos y utilidades como Pick, Omit o Partial para garantizar consistencia en arquitecturas complejas.

Este enfoque previene errores en tiempo de compilación, mejora la mantenibilidad y facilita la colaboración en equipos grandes. Por ejemplo, en un frontend con múltiples vistas y estados, definir tipos para props, estados y eventos desde el inicio evita "type drift" (deriva de tipos) donde las interfaces se desincronizan con la implementación real.

Cómo funciona en la práctica

Comienza identificando las entidades principales de tu aplicación. Para un e-commerce, podrían ser Product, User, Order. Define interfaces base:

interface Product {
  id: string;
  name: string;
  price: number;
  category: string;
}

interface User {
  id: string;
  email: string;
  role: 'admin' | 'customer';
}

Luego, usa tipos genéricos para crear variantes. Por ejemplo, un tipo para productos en carrito:

type CartItem = T & {
  quantity: number;
  addedAt: Date;
};

// Uso:
const item: CartItem = {
  id: "123",
  name: "Laptop",
  price: 999,
  category: "Electronics",
  quantity: 1,
  addedAt: new Date()
};

Finalmente, estructura los módulos: crea archivos como types/product.ts, types/user.ts y exporta tipos para usarlos en componentes React o servicios.

Caso de estudio

Supongamos una app de gestión de tareas con arrastrar y soltar. Primero, modelamos el estado:

type TaskStatus = 'todo' | 'inProgress' | 'done';

interface Task {
  id: string;
  title: string;
  description?: string;
  status: TaskStatus;
  assigneeId?: string;
}

interface Column {
  id: string;
  title: string;
  taskIds: string[];
}

interface BoardState {
  tasks: Record;
  columns: Record;
  columnOrder: string[];
}

Usamos tipos para eventos de drag-and-drop:

type DragStartEvent = {
  type: 'task';
  taskId: string;
  sourceColumnId: string;
};

type DropEvent = DragStartEvent & {
  targetColumnId: string;
};

function handleDrop(event: DropEvent, state: BoardState): BoardState {
  // Lógica para actualizar estado tipado
  return updatedState;
}

Esto asegura que los componentes reciban datos consistentes, reduciendo bugs en interacciones complejas.

Errores comunes

  • Tipos demasiado amplios: Usar any o unknown sin necesidad, perdiendo ventajas de TypeScript. Solución: Define tipos específicos; usa unknown solo para datos externos con validación.
  • Duplicación de interfaces: Crear User y UserDTO separados sin herencia. Solución: Extiende interfaces base o usa tipos utilitarios: type UserDTO = Pick.
  • Ignorar tipos genéricos en librerías: No parametrizar componentes React con props tipados. Solución: Usa React.ComponentType para mayor seguridad.
  • Tipos desactualizados: Modificar implementación sin actualizar tipos. Solución: Integra verificación en CI/CD con tsc --noEmit.
  • No planificar estados asíncronos: Dejar tipos para loading/error sin definir. Solución: Usa tipos unidos: type AsyncState = { loading: true } | { data: T } | { error: string }.

Checklist de dominio

  1. He definido todas las entidades principales del dominio con interfaces o tipos.
  2. He utilizado tipos genéricos para crear variantes reutilizables (ej., PaginatedResponse).
  3. He estructurado tipos en archivos lógicos (ej., carpeta types/) y evitado ciclos de dependencia.
  4. He modelado estados complejos (ej., formularios, drag-and-drop) con tipos unidos o intersecciones.
  5. He integrado tipos para librerías externas (ej., React Router, Redux) usando declaraciones o genéricos.
  6. He escrito tests de tipos con herramientas como tsd para verificar contratos.
  7. He documentado tipos clave con comentarios JSDoc para mejorar la colaboración.

Diseñar tipos para un sistema de comentarios en tiempo real

Implementa un sistema de tipos para una funcionalidad de comentarios en tiempo real en una aplicación frontend. Sigue estos pasos:

  1. Crea una interfaz Comment con propiedades: id (string), content (string), authorId (string), timestamp (Date), y replies (array de Comment).
  2. Define un tipo genérico ApiResponse que incluya campos para data (T), status (200, 400, 500), y message (string opcional).
  3. Modela el estado de la UI con un tipo unido: CommentsUIState que pueda ser 'loading' | 'loaded' | 'error'.
  4. Crea una función tipada addReply(commentId: string, reply: Comment): ApiResponse que simule añadir una respuesta.
  5. Escribe un ejemplo de uso en un archivo TypeScript, mostrando cómo se pasarían estos tipos a un componente React.
Pistas
  • Usa Partial para permitir comentarios incompletos durante la creación.
  • Considera usar Record para almacenar comentarios por ID en el estado.
  • Para el tipo unido, define cada variante como un objeto con una propiedad discriminante (ej., { state: 'loading' }).

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.