Tres patrones de TypeScript que mejoran la arquitectura de las aplicaciones React
Aprende cómo los patrones Repository, Observer y Builder utilizan el sistema de tipos de TypeScript para crear bases de código más limpias y fáciles de mantener en React y Next.js.
Escribir en TypeScript no significa usarlo bien
Muchos desarrolladores añaden anotaciones de tipo a sus variables y consideran que ya han hecho suficiente. Pero la verdadera fortaleza de TypeScript radica en otro lugar: en los patrones estructurales que separan una base de código frágil de una que crece de manera ordenada con el tiempo.
Habiendo dedicado tiempo a desarrollar aplicaciones en producción con React y Next.js, hay algunos patrones que se destacan como verdaderos cambios revolucionarios. Estos no son ejercicios abstractos de libros de texto; son soluciones a problemas con los que realmente te encontrarás.
1. Patrón de Repositorio: Separa la obtención de datos de todo lo demás
Cuando tus componentes llaman directamente a fetch(), te expones a una reescritura dolorosa en cuanto la API cambie su estructura.
El Patrón de Repositorio encapsula el acceso a los datos detrás de una interfaz limpia:
// repositories/userRepository.ts
interface UserRepository {
getById(id: string): Promise<User>;
getAll(): Promise<User[]>;
}
export class ApiUserRepository implements UserRepository {
async getById(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
async getAll(): Promise<User[]> {
const res = await fetch('/api/users');
return res.json();
}
}
Con esto en su lugar, sus componentes dependen de una abstracción en lugar de de una implementación concreta. Eso significa que puede reemplazar ApiUserRepository por una versión de simulación en sus pruebas sin tocar ningún código de interfaz.
2. Patrón Observer: Estado reactivo sin la sobrecarga de Redux
Redux cumple con su función, pero para estados que no son especialmente complejos, puede parecer como usar un martillo pesado para una tarea sencilla. Una implementación ligera de Observer basada en clases y construida sobre un emisor de eventos tipado ofrece una alternativa más simple:
// utils/eventBus.ts
type EventMap = {
'user:loggedIn': { userId: string };
'cart:updated': { itemCount: number };
};
class TypedEventBus {
private listeners: Partial<{
[K in keyof EventMap]: ((payload: EventMap[K]) => void)[]
}> = {};
on<K extends keyof EventMap>(event: K, cb: (payload: EventMap[K]) => void) {
(this.listeners[event] ??= []).push(cb);
}
emit<K extends keyof EventMap>(event: K, payload: EventMap[K]) {
this.listeners[event]?.forEach(cb => cb(payload));
}
}
export const eventBus = new TypedEventBus();
Todo aquí está fuertemente tipado: no hay tipos any ocultos. Eso significa que no existe ambigüedad sobre qué formato de carga esperar un determinado listener.
3. Patrón Builder: Organizar la creación de objetos complejos
Si alguna vez has tenido dificultades para crear objetos de filtro, configuraciones de solicitudes API o esquemas de formularios repletos de condiciones anidadas, el Patrón Constructor aporta una claridad inmediata:
// builders/queryBuilder.ts
class QueryBuilder {
private params: Record<string, string> = {};
withPage(page: number) {
this.params['page'] = String(page);
return this;
}
withLimit(limit: number) {
this.params['limit'] = String(limit);
return this;
}
withSearch(term: string) {
if (term.trim()) this.params['q'] = term;
return this;
}
build(): string {
return new URLSearchParams(this.params).toString();
}
}
// Usage
const query = new QueryBuilder()
.withPage(1)
.withLimit(20)
.withSearch('typescript')
.build();
// → "page=1&limit=20&q=typescript"
El código resultante se lee de forma natural, las operaciones se concatenan de manera ordenada y es estructuralmente imposible ejecutar los pasos en el orden incorrecto.
Puntos clave
- Patrón Repository: separa la capa de acceso a datos de la lógica de interfaz de usuario, siguiendo el principio de inversión de dependencias
- Patrón Observer: permite una comunicación ligera y reactiva entre las partes de la aplicación sin necesidad de código genérico excesivo
- Patrón Constructor: hace que la creación de objetos complejos sea al mismo tiempo legible y segura
- Todos estos tres patrones se benefician enormemente de las interfaces y generics de TypeScript, lo que los hace notablemente más limpios que sus equivalentes en JavaScript puro
“TypeScript no se trata solo de agregar tipos: crea un diálogo entre cada capa de tu aplicación.” — Dan Vanderkam, autor de Effective TypeScript
Qué probar a continuación
Elige un patrón de este artículo y úsalo en un módulo real de tu código esta semana. Trabaja con una sección a la vez, y recuerda que estos patrones son herramientas, no dogmas.
Los mejores conjuntos de código no se definen por la cantidad de tipos que contienen, incluso en un proyecto TypeScript. Se definen por su intencionalidad: cada patrón está allí porque se ha ganado ese lugar.
Lecturas relacionadas
- El compilador de TypeScript en Go y la ejecución nativa: una guía de migración — Aprenda cómo el compilador basado en Go de TypeScript y la ejecución nativa de Node.js afectarán los proyectos React y Next.js, y qué debe corregir ahora en su tsconfig.
- Generar automáticamente un cliente de API type-safe para Next.js a partir de NestJS Swagger — Aprenda cómo eliminar tipos de API duplicados utilizando NestJS Swagger y Orval para generar automáticamente ganchos React Query type-safe para Next.js.