Зарегистрируйтесь, чтобы продолжить обучение

Отрисовка (рендеринг) состояния JS: Архитектура фронтенда

Обновление внешнего вида на основе состояния обычно выносят в отдельный слой, который называется представлением (View). В простейшем случае представлением является функция, которая принимает на вход состояние, анализирует его и производит необходимые изменения в DOM.

// Лучше вынести в отдельный файл, так как обычно там много кода
import render from './view.js';

const state = /* данные состояния */;
const input = document.querySelector(/* .... */);

input.addEventListener('keyup', (e) => {
  state.registrationForm.value = e.target.value;
  if (input.value === '' || input.value.match(/^\d+$/)) {
    state.registrationForm.valid = true;
    state.registrationForm.errors = [];
  } else {
    state.registrationForm.valid = false;
    state.registrationForm.errors.push('wrong format');
  }

  render(state);
});

Пример того, как render() может быть устроена внутри:

const render = (state) => {
  const submit = document.querySelector(/* .... */);
  const input = document.querySelector(/* .... */);

  submit.disabled = !state.registrationForm.valid;

  if (state.registrationForm.valid) {
    input.style.border = null;
  } else {
    input.style.border = 'thick solid red';
  }
};

У такого подхода могут быть проблемы с производительностью из-за необходимости постоянно выполнять поиск элементов по DOM. Чтобы избежать этого, можно превратить представление в объект и сохранить в него все нужные элементы ровно один раз, в момент инициализации.

// реализация c помощью классов:

class View {
  constructor(){
    this.submit = null;
    this.input = null;
  }

  init() {
    this.submit = document.querySelector(/* .... */);
    this.input = document.querySelector(/* .... */);
  }

  render(state) {
    this.submit.disabled = !state.registrationForm.valid;

    if (state.registrationForm.valid) {
      this.input.style.border = null;
    } else {
      this.input.style.border = 'thick solid red';
    }
  }
}

const view = new View();
view.init(); // тут делаем выборки
view.render(state);

// реализация с помощью функций:

const init = () => {
  const submit = document.querySelector(/* .... */);
  const input = document.querySelector(/* .... */);
  const state = /* данные состояния */;
  // ...
  return {
    state,
    elements: {
      input,
      submit,
    },
  };
};

const render = (elements) => { ... };

const elements = init();
render(elements);

С ростом приложения увеличивается и количество обработчиков. Каждый из них может приводить к изменению только части страницы. Как поступать в таком случае? Создавать по рендеру на каждую ситуацию или описывать все возможные изменения в одной функции рендер?

Наиболее простым решением будет привязка таких функций к элементам состояния. Предположим, что у нас есть страница управления списком уроков в курсе. В состоянии это выглядит так:

const state = {
  lessons: [/* список уроков */],
  // остальная часть состояния
};

Для отрисовки этого списка подойдет одна функция renderLessons(), которая будет вызываться во всех обработчиках, изменяющих этот список: удаляющих или добавляющих элементы.

// Добавление
el1.addEventListener('submit', (e) => {
  // логика
  renderLessons(state.lessons);
})

// Удаление
el2.addEventListener('submit', (e) => {
  // логика
  renderLessons(state.lessons);
})

И самое интересное. Что происходит внутри этой функции? Кажется, что внутри функции рендеринга нужно определять, что произошло, и затем менять необходимую часть DOM, например, удалить какой-то элемент, которого больше нет. В реальности это очень затратный подход, его сложно программировать, так как появляется большое количество условных конструкций. Намного проще выполнять полную перерисовку в любой ситуации. Тогда код останется максимально простым.

У такого подхода есть серьезный недостаток – производительность. Но учитывайте два важных момента. Во-первых, производительность — далеко не всегда проблема. Например, при реализации автокомплитов именно так и нужно поступать. Все будет работать быстро в любом случае. Во-вторых, именно эту проблему решают современные фронтенд-фреймворки. Они сами знают, как эффективнее всего обновить DOM.

Теперь наше приложение разделено на три независимых части: состояние (данные приложения), обработчики и рендеринг. Эта модель работы на тривиальных приложениях (в пару-тройку обработчиков) смотрится избыточной, но если обработчиков станет хотя бы 10, то вы увидите, что с приложением достаточно удобно работать. Виден поток данных, то есть движение данных в приложении от одних частей к другим, от обработчика до отрисовки в DOM. Всегда можно отследить, что изменилось, и как одни части приложения зависят от других. К тому же, сокращается дублирование. Например, изменение состояния может идти из разных частей приложения, но логика отрисовки при этом остается неизменной. В такой ситуации достаточно описать новый способ изменения уже существующего состояния, а рендеринг сделает все остальное.

Кроме наличия разделения на три части, не менее важно то, как они друг с другом взаимодействуют:

  • Состояние не знает ничего про остальные части системы — оно ядро
  • Рендеринг пользуется состоянием для отрисовки (добавление, изменение или удаление элементов) и добавляет новые обработчики в DOM
  • Обработчики знают про состояние, так как обновляют его и инициируют рендеринг

Этот способ разделения по-прежнему обладает одним важным недостатком, который мы устраним в уроке, посвященном MVC.


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

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

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

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 26 декабря
профессия
от 39 525 ₸ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 26 декабря

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

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

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

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