Regístrate para acceder a más de 15 cursos gratuitos de programación con un simulador

MVC JS: Arquitectura Front-end

Como se mencionó antes, una limitación importante de nuestro enfoque para manejar el estado es que los controladores son los encargados de invocar la función de renderizado. A continuación, se muestra un ejemplo que demuestra cómo se realiza la llamada a render().

input.addEventListener('change', () => {
  const { registrationProcess } = state;
  if (input.value === '') {
    registrationProcess.validationState = 'valid';
    registrationProcess.errors = [];
  } else if (!input.value.match(/^\d+$/)) {
    registrationProcess.validationState = 'invalid';
    registrationProcess.errors = ['Bad format'];
  } else {
    registrationProcess.validationState = 'valid';
    registrationProcess.errors = [];
  }

  render(state);
});

¿Qué problemas pueden surgir con este enfoque?

Aquí es importante mencionar que este enfoque es válido en el backend, porque el backend funciona dentro de una arquitectura cliente-servidor. El controlador en el backend es una función que cambia el estado (lo cual no conlleva a ningún renderizado ya que se realiza una redirección) o extrae datos de la base de datos para formar una respuesta, por ejemplo, en forma de HTML. En el frontend, los cambios en los datos tienen un impacto inmediato en la pantalla.

El ejemplo que vemos arriba es muy simplificado: solo se llama a una función render que recibe todo el estado como argumento. Ahora imagina que tenemos docenas de controladores en nuestra aplicación (lo cual no es mucho) y un estado grande (lo cual es típico). En esta situación, volver a renderizar todo en cada cambio puede ser una operación costosa. Por otro lado, se puede agregar una verificación dentro de render para cada parte del estado y rastrear si ha cambiado. Este enfoque rápidamente se convierte en un problema en sí mismo. Es fácil olvidar verificar algo, cometer un error en la verificación o simplemente olvidar actualizar la verificación después de cambiar la estructura del estado.

Existe otra forma de abordar esta tarea. Se basa en un patrón de diseño llamado Observador (Observer). La idea es simple: una parte del sistema observa los cambios en otra parte del sistema. Si el objeto observado cambia, el observador puede hacer algo útil.

En JavaScript, se puede implementar un mecanismo similar utilizando Proxy, pero esto es bastante complicado. Una solución más sencilla sería utilizar una biblioteca existente como on-change.

import onChange from 'on-change';

const app = () => {
  const state = {
    ui: {
      value: 'hello',
    },
  };

  const watchedState = onChange(state, (path, value, previousValue) => {
    alert('¡El valor ha cambiado!');
    console.log(path);
    // => 'ui.value'
    console.log(value);
    // => 'other value'
    console.log(previousValue);
    // => 'hello'
  });

  // Después de cambiar el atributo, se mostrará una alerta
  const el = document.querySelector('<selector>');
  el.addEventListener('change', () => {
    watchedState.ui.value = 'other value';
  });
}

// En algún otro archivo (generalmente en index.js)
app();

El evento "on-change" permite detectar cambios en partes específicas del estado y activar funciones de renderizado cuando estos cambios ocurren. La decisión sobre qué partes del estado monitorear y cuántos "watchers" utilizar depende de las necesidades específicas del proyecto. En proyectos simples, como los educativos, a menudo es suficiente con un único "watcher" que controle todo el estado. Sin embargo, en escenarios más complejos y reales, los "watchers" se configuran de manera que sean convenientes y eficientes para cada situación particular.

See the Pen js_dom_mvc_watch by Códica (@hexlet) on CodePen.

Ahora los controladores no saben nada sobre el renderizado y solo se encargan de interactuar con el estado. A su vez, el renderizado sigue el estado y solo cambia el DOM donde sea necesario y de la manera correcta. Este enfoque de organización de la aplicación se considera clásico y se conoce como MVC (Modelo Vista Controlador). Cada palabra representa una capa de la aplicación con su propia responsabilidad. El Modelo es el estado de la aplicación y la lógica del negocio, la Vista es la capa que interactúa con el DOM y el Controlador son los controladores.

Es importante tener en cuenta que Modelo, Controlador o Vista no son archivos, clases o algo concreto. Son capas lógicas que cumplen su función y se comunican entre sí de cierta manera.

El entendimiento de MVC proporciona una respuesta sobre cómo estructurar una aplicación, pero rara vez se implementa por sí solo. Los frameworks modernos se basan en diferentes modificaciones de MVC y ya han establecido las reglas de interacción. Solo queda entenderlas y seguirlas.

MVC

Lo más importante en esta imagen son las flechas entre las capas. Estas definen las barreras de abstracción. Quién puede interactuar con quién y cómo. Por ejemplo, en este diagrama no hay una flecha del controlador a la vista. Esto significa que el controlador no puede cambiar la vista sin pasar por el modelo. Lo que se muestra en la pantalla es una representación del estado de la aplicación y nada más. El siguiente código se considera una violación:

// Supongamos que hay un formulario en la página
// con un campo de entrada para una tarea y un botón para agregarla

const form = document.querySelector('form');
const input = document.querySelector('form input');
form.addEventListener('submit', () => {
  watchedState.registrationProcess.state = 'processing';
  // Hacer algo con los datos, por ejemplo, agregarlos al estado
  input.value = ''; // ¡Borrar el campo de entrada directamente! Violación de MVC
});

En el diagrama, tampoco hay una flecha de la vista al modelo. Esto significa que la capa de vista no puede cambiar el modelo mientras está en funcionamiento:

const watchedState = onChange(state, (path) => {
  if (path === 'registrationProcess.state') {
    // ¡Se está actualizando el estado! Violación de MVC
    watchedState.registrationProcess.alert = 'Enviando datos...';
  }
});

Y, por supuesto, la vista no puede actuar como un controlador y realizar, por ejemplo, solicitudes HTTP:

const watchedState = onChange(state, (path, value) => {
  if (path === 'registrationProcess.state') {
    // ¡Hacer una solicitud HTTP! Violación de MVC
    if (value === 'sending') {
      axios.post(endpoint, watchedState.registrationProcess.data);
    }
  }
});

En resumen, el controlador hace algo con los datos, la vista responde a los cambios en los datos y cambia el DOM.


Materiales adicionales

  1. Backbone MVC
  2. MVC de forma sencilla

Para acceder completo a curso necesitas un plan básico

El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.

Obtener acceso
130
cursos
1000
ejercicios
2000+
horas de teoría
3200
test

Obtén acceso

Cursos de programación para principiantes y desarrolladores experimentados. Comienza tu aprendizaje de forma gratuita

  • 130 cursos, 2000+ horas de teoría
  • 1000 ejercicios prácticos en el navegador
  • 360 000 estudiantes
Al enviar el formulario, aceptas el «Política de privacidad» y los términos de la «Oferta», y también aceptas los «Términos y condiciones de uso»

Nuestros graduados trabajan en empresas como:

Bookmate
Health Samurai
Dualboot
ABBYY