JS: Архитектура фронтенда
Теория: Отрисовка (рендеринг) состояния
Наличие выделенного состояния позволяет легче видеть данные с которыми мы работаем и создает единый источник правды, но не избавляет нас от сложной логики в обработчиках. Там все равно, помимо изменения состояния приходится обновлять UI. Для полной картины нужно поправить и эту часть, отделив логику работы с UI от логики работы с состоянием.
Обновление внешнего вида на основе состояния обычно выносят в отдельный слой, который называется представлением (View). В простейшем случае представлением является функция, которая принимает на вход состояние, анализирует его и производит необходимые изменения в DOM.
Возьмем пример с формой из прошлого урока и изменим его так, что обновление UI будет изолировано в функции render().
Разбор изменений
- Валидация вынесена в отдельную функцию validate(value). Теперь можно использовать эту функцию повторно в других местах.
- Функция render() отвечает за отрисовку интерфейса. Вся логика обновления DOM теперь отделена от логики обновления состояния.
- Используется classList.toggle для управления стилями. Вместо ручной установки style.border, теперь применяем CSS-классы.
Преимущество такого подхода начинает проявляться как только у нас добавляются новые обработчики, влияющие на ту же часть UI. Для примера добавим поле email.
Что изменилось?
-
Добавили поле
email- Теперь у нас два поля с независимой валидацией.
-
Вынесли
validatePhoneиvalidateEmailв отдельные функции- Теперь легко добавить дополнительные правила или другие поля.
-
Создали
validateForm() -
Проверяет все поля разом.
- Записывает ошибки в
state.registrationForm.errors. - Определяет, можно ли отправлять форму.
- Записывает ошибки в
-
Обновили
render()- Универсально обновляет состояние формы. Обратите внимание, что она вызывается два раза, для первой отрисовки когда никаких событий не было и по событию.
- Теперь ошибки и стили обновляются централизованно.
-
Добавили
handleInput()- Одна универсальная функция для обновления полей.
- Упрощает код обработчиков событий.
Несмотря на то, что кода стало больше, уровень его сложности почти не вырос. Внутри мы работаем с отдельными частями (управление состоянием, отрисовка, обработчики), которые сами по себе достаточно простые и односложные. Такое разделение логики масштабируется даже на большие формы с десятками полей.
Частичное обновление VS Полное обновление
С ростом приложения увеличивается и количество обработчиков. Каждый из них может приводить к изменению только части страницы. Как поступать в таком случае? Создавать по рендеру на каждую ситуацию или описывать все возможные изменения в одной функции render()?
Наиболее простым решением будет привязка таких функций к элементам состояния. Предположим, что у нас есть страница управления списком уроков в курсе. В состоянии это выглядит так:
Для отрисовки этого списка подойдет одна функция renderLessons(), которая будет вызываться во всех обработчиках, изменяющих этот список: удаляющих или добавляющих элементы.
Что происходит внутри этой функции? Кажется, что внутри функции render() нужно соотносить данные в объекте состояния и то, что отображено на экране, а затем менять необходимую часть DOM, например, удалить какой-то элемент, которого больше нет. Посмотрите на то, как бы тогда выглядела функция renderLessons():
В реальности это очень затратный подход, его сложно программировать, так как появляется большое количество условных конструкций. Намного проще выполнять полную перерисовку на любое событие. Тогда код останется максимально простым.
Этот код намного проще. Он в несколько раз короче и внутри него нет ни одной условной конструкции.
У такого подхода есть серьезный недостаток – производительность. Но учитывайте два важных момента. Во-первых, производительность — далеко не всегда проблема. Например, при реализации автокомплитов именно так и нужно поступать. Все будет работать быстро в любом случае. Во-вторых, именно эту проблему решают современные фронтенд-фреймворки. Они сами знают, как эффективнее всего обновить DOM.
Теперь наше приложение разделено на три независимых части: состояние (данные приложения), обработчики и рендеринг. Эта модель работы на тривиальных приложениях (в пару-тройку обработчиков) смотрится избыточной, но если обработчиков станет хотя бы 10, то вы увидите, что с приложением достаточно удобно работать. Виден поток данных, то есть движение данных в приложении от одних частей к другим, от обработчика до отрисовки в DOM. Всегда можно отследить, что изменилось, и как одни части приложения зависят от других. К тому же, сокращается дублирование. Например, изменение состояния может идти из разных частей приложения, но логика отрисовки при этом остается неизменной. В такой ситуации достаточно описать новый способ изменения уже существующего состояния, а рендеринг сделает все остальное.
Кроме наличия разделения на три части, не менее важно то, как они друг с другом взаимодействуют:
- Состояние не знает ничего про остальные части системы — оно ядро.
- Рендеринг пользуется состоянием для отрисовки (добавление, изменение или удаление элементов) и добавляет новые обработчики в DOM.
- Обработчики знают про состояние, так как обновляют его и инициируют рендеринг.
Этот способ разделения по-прежнему обладает одним важным недостатком - изменение состояние требует явного вызова функции отрисовки. Этот недостаток мы устраним в уроке посвященному MVC.
Рекомендуемые программы
Завершено
0 / 11




