Как говорилось ранее, наша схема работы с состоянием имеет один существенный недостаток — за вызов отрисовки отвечают обработчики. Ниже приведён пример, демонстрирующий вызов 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 и за нас определили правила взаимодействия. Остаётся только разобраться и следовать им.
Самое важное на этой картинке – стрелки между слоями. Они определяют барьеры абстракции. Кто с кем и как может взаимодействовать, а кто нет. Например, на этой диаграмме нет стрелки из контроллера в представление. Это обозначает, что контроллер не может (не может!) менять представление минуя модель. То, что отражено на экране — это отображение состояния приложения и никак иначе. Такой код считается нарушением:
// Предположим, что на странице есть одна форма
// с полем для ввода задачи и кнопкой для её добавления
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.
Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт