Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

MVC JS: Архитектура фронтенда

Как говорилось ранее, наша схема работы с состоянием имеет один существенный недостаток — за вызов отрисовки отвечают обработчики. Ниже приведён пример, демонстрирующий вызов 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);
});

Какие проблемы могут возникнуть при таком подходе?

Здесь стоит сказать, что на бэкенде такой подход как раз оправдан. Бэкенд работает в рамках другой парадигмы, а именно клиент-серверной архитектуры. Обработчик на бэкенде по своей сути это функция, которая либо меняет состояние (что не приводит ни к каким перерисовкам, так как выполняется редирект), либо извлекает данные из базы для формирования ответа, например, в виде HTML. Во фронтенде изменение данных тут же влияет на экран.

Пример, который мы видим выше, очень упрощён: в нём вызывается только одна функция render, принимающая на вход всё состояние. Теперь представьте, что у нас в приложении десятки обработчиков (что немного) и большое состояние (что типично). В такой ситуации перерисовывать всё на каждое изменение довольно затратная операция. С другой стороны, можно вставить проверку внутри render на каждый кусок состояния и отслеживать, изменился ли он. Такой подход очень быстро станет проблемой сам по себе. Можно легко забыть что-то проверить, можно ошибиться в проверке, можно просто забыть поправить проверку после изменения структуры состояния.

Существует другой способ выполнить эту задачу. Он основан на такой концепции (говорят шаблон проектирования), как Наблюдатель (Observer). Его идея очень проста: одна часть системы наблюдает за изменением другой части системы. Если наблюдаемый изменился, то наблюдатель может сделать что-то полезное.

В JS подобный механизм можно реализовать через Proxy, но это довольно сложно. Более простым решением будет использование готовой библиотеки on-change.

import onChange from 'on-change';

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

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

  // После изменения атрибута возникнет алерт
  const el = document.querySelector('<selector>');
  el.addEventListener('change', () => {
    watchedState.ui.value = 'other value';
  });
}

// Где-то в другом файле (обычно в index.js)
app();

On-change позволяет "слушать" нужные части состояния и вызывать функции рендеринга при их изменении. То, какие части конкретно слушать и сколько вешать "вотчеров", зависит от задачи. В примитивных ситуациях достаточно одного вотчера на весь стейт (скорее это учебный проект), в реальных же ситуациях вотчеры делают так, чтобы было удобно (в каждой ситуации по-разному).

See the Pen js_dom_mvc_watch by Hexlet (@hexlet) on CodePen.

Теперь обработчики ничего не знают про рендеринг и отвечают только за взаимодействие с состоянием. В свою очередь рендеринг следит за состоянием и меняет DOM только там, где нужно и так, как нужно. Этот способ организации приложения считается уже классическим и носит имя MVC (Model View Controller). Каждое слово обозначает слой приложения со своей зоной ответственности. Model — состояние приложения и бизнес-логика, View — слой, отвечающий за взаимодействие с DOM, Controller — обработчики.

Обратите внимание на то, что Model, Controller или View — это не файлы, не классы, ни что-либо еще конкретное. Это логические слои, которые выполняют свою задачу и определённым образом взаимодействуют друг с другом.

Понимание MVC даёт ответ на то, как структурировать приложение, но самостоятельно его реализуют редко. Современные фреймворки построены на различных модификациях MVC и за нас определили правила взаимодействия. Остаётся только разобраться и следовать им.

MVC

Самое важное на этой картинке – стрелки между слоями. Они определяют барьеры абстракции. Кто с кем и как может взаимодействовать, а кто нет. Например, на этой диаграмме нет стрелки из контроллера в представление. Это обозначает, что контроллер не может (не может!) менять представление минуя модель. То, что отражено на экране — это отображение состояния приложения и никак иначе. Такой код считается нарушением:

// Предположим, что на странице есть одна форма
// с полем для ввода задачи и кнопкой для её добавления

const form = document.querySelector('form');
const input = document.querySelector('form input');
form.addEventListener('submit', () => {
  watchedState.registrationProcess.state = 'processing';
  // Что-то делаем с данными, например, добавляем в состояние
  input.value = ''; // Очистка поля ввода напрямую! Нарушение MVC!
});

На диаграмме также отсутствует стрелка из представления в модель. Это значит, что слой представления не может менять модель во время своей работы:

const watchedState = onChange(state, (path) => {
  if (path === 'registrationProcess.state') {
    // Обновляется состояние! Нарушение MVC!
    watchedState.registrationProcess.alert = 'Sending data...';
  }
});

И, конечно, представление не может притворяться контроллером и выполнять, например, HTTP-запросы:

const watchedState = onChange(state, (path, value) => {
  if (path === 'registrationProcess.state') {
    // Делаем HTTP-запрос! Нарушение MVC!
    if (value === 'sending') {
      axios.post(endpoint, watchedState.registrationProcess.data);
    }
  }
});

Итого: контроллер что-то делает с данными, на изменение данных срабатывает слой представления и изменяет DOM.


Дополнительные материалы

  1. Backbone MVC
  2. Что такое MVC: рассказываем простыми словами

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов для веб-приложений
6 октября 10 месяцев
Иконка программы Fullstack-разработчик
Профессия
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
6 октября 16 месяцев

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и соглашаетесь с «Условиями использования»