MVC Pattern en Aplicaciones Frontend Modernas
Aplica el Model-View-Controller pattern en aplicaciones React y Vue para separar datos, UI y logica de interaccion en una arquitectura de componentes mantenible
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
MVC Pattern en Aplicaciones Frontend Modernas
Model-View-Controller separa una aplicacion en tres componentes: Model (datos y reglas), View (presentacion) y Controller (manejo de entrada y coordinacion). Aunque frameworks como React y Vue difuminan estos limites, aplicar disciplina MVC previene que los componentes se conviertan en mezclas inmantenibles de estado, UI y efectos secundarios.
Cuando Usar Esto
- Los componentes crecen mas de 200 lineas porque mezclan fetching de datos, transformacion y renderizado
- La misma logica de datos se duplica a traves de multiples paginas
- Testear la UI requiere mockear redes, stores y DOM simultaneamente
Problema
Un componente React que obtiene usuarios, filtra por busqueda, ordena por nombre, pagina resultados y renderiza tarjetas es imposible de testear o reutilizar.
Solucion
// model/UserModel.ts
interface User {
id: string;
name: string;
email: string;
role: string;
}
class UserModel {
private users: User[] = [];
setUsers(users: User[]) {
this.users = users;
}
getFilteredUsers(query: string): User[] {
if (!query) return this.users;
const lower = query.toLowerCase();
return this.users.filter(u =>
u.name.toLowerCase().includes(lower) ||
u.email.toLowerCase().includes(lower)
);
}
getSortedUsers(field: keyof User, direction: 'asc' | 'desc'): User[] {
return [...this.users].sort((a, b) => {
const cmp = String(a[field]).localeCompare(String(b[field]));
return direction === 'desc' ? -cmp : cmp;
});
}
}
// controller/UserController.ts
class UserController {
constructor(private model: UserModel) {}
async loadUsers(): Promise<void> {
const res = await fetch('/api/users');
const users = await res.json();
this.model.setUsers(users);
}
search(query: string): User[] {
return this.model.getFilteredUsers(query);
}
sort(field: keyof User, direction: 'asc' | 'desc'): User[] {
return this.model.getSortedUsers(field, direction);
}
}
// view/UserListView.tsx
import { useState, useEffect } from 'react';
function UserListView({ controller }: { controller: UserController }) {
const [users, setUsers] = useState<User[]>([]);
const [query, setQuery] = useState('');
useEffect(() => {
controller.loadUsers().then(() => {
setUsers(controller.search(''));
});
}, []);
const handleSearch = (q: string) => {
setQuery(q);
setUsers(controller.search(q));
};
return (
<div>
<input
type="search"
value={query}
onChange={e => handleSearch(e.target.value)}
placeholder="Buscar usuarios..."
/>
<ul>
{users.map(u => (
<li key={u.id}>{u.name} — {u.email}</li>
))}
</ul>
</div>
);
}
Variaciones
- MVVM: ViewModel expone propiedades observables que la View enlaza directamente
- MVP: Presenter actualiza la View imperativamente en lugar de la View observando estado
- Flux/Redux: Flujo de datos unidireccional con un dispatcher central reemplazando al Controller
Mejores Practicas
- Manten los Models puros — sin efectos secundarios, sin referencias al DOM
- Los Controllers orquestan pero no saben como se renderizan los datos
- Las Views son delgadas — reciben datos y emiten eventos, no contienen reglas de negocio
Errores Comunes
- Poner logica de fetch dentro de la View en lugar del Controller
- Hacer que el Model sea consciente de manejo de estado especifico del framework
- Permitir que la View mute directamente los datos del Model
FAQ
P: React ya maneja MVC con hooks y context? R: React proporciona bloques de construccion, no arquitectura. Los hooks manejan estado local; no enforcean separacion de preocupaciones. MVC agrega disciplina.
P: Cuando deberia usar Redux en lugar de MVC? R: MVC funciona bien para funcionalidades localizadas. Redux brilla cuando multiples componentes no relacionados necesitan los mismos datos o cuando el debugging con time-travel es valioso.