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

Комплексное состояние JS: Архитектура фронтенда

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

Можно подумать, что анимация через CSS не меняет ничего в нашем приложении. Да, анимация в CSS не связана с нашим приложением, но внутри браузера это состояние есть, и оно меняется.

Отличным примером состояния служит форма. Представьте себе поле для ввода телефона, которое отслеживает ошибки при вводе и сразу их показывает. Если ошибок нет, то оно позволяет выполнить отправку формы, иначе кнопка заблокирована. Что в данном случае является состоянием? Во-первых, все данные, вводимые в форму. Во-вторых, состояние корректности (валидности) данных формы: «валидно» и «не валидно». На основе этого состояния определяется, обводить красной рамкой поле ввода или нет. Ну и сами ошибки, которые нужно выводить, тоже являются частью состояния.

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

Как видно из примера, состояние описывается обычным JS-объектом, который создается при старте приложения:

// Константа потому что объект состояния всегда остается тем же
const state = {
  // Состояние формы выделено в отдельное свойство,
  // так как форм на странице может быть много
  registrationForm: {
    valid: true,
    errors: [],
  }
};

А вот логика обработчика разделилась на два независимых процесса: первый – изменение состояния, второй – отображение этого состояния на экране:

input.addEventListener('input', (e) => {
  const inputValue = e.target.value;
  // Сохраняем вводимые данные
  state.registrationForm.value = inputValue;
  // В зависимости от введенного значения определяется состояние формы, ее валидность.
  // Если значение неверное, то помечаем форму невалидной,
  // иначе помечаем форму валидной
  if (inputValue.match(/^\d+$/)) {
    state.registrationForm.valid = true;
    state.registrationForm.errors = [];
  } else {
    state.registrationForm.valid = false;
    state.registrationForm.errors.push('wrong format');
  }

  // Заблокированность кнопки определяется состоянием валидности
  submit.disabled = !state.registrationForm.valid;

  // Наличие ошибок (красная рамка) зависит от состояния валидности формы
  if (state.registrationForm.valid) {
    input.style.border = null;
  } else {
    input.style.border = 'thick solid red';
  }
});

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

Рекомендации по организации состояния

Что хранится в состоянии

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

// Не очень
const state = {
  // тут всякое
  httpTimeout: 1000,
  apiKey: 'my super secret hash',
};

// Очень
const httpTimeout = 1000;
const apiKey = 'my super secret hash';

Объект состояния

Само состояние лучше всего организовывать в виде одной константы, содержащей объект. За таким объектом гораздо проще следить, чем за набором разных констант или переменных. Более того, как вы увидите позже, это позволяет реализовать удобное отслеживание изменений для реализации архитектуры MVC, к которой мы медленно подбираемся.

// Не очень
const state = {
  formState: { /* ... */ },
};

const posts = [];
let connected = false;

// Очень
const state = {
  formState: { /* ... */ },
  posts: [],
  connected: false,
};

Именование

Выделенное состояние само по себе — это уже отлично, но не менее важно правильное именование данных внутри состояния. Что, например, означают свойства valid или connected в объекте состояния догадаться можно, но к чему они относятся, без анализа кода понять невозможно. Эти слова слишком общие, они не задают контекст. Лучше всегда уточнять что имеется в виду:

// не очень
const state = {
  valid: false,
  connected: true,
}

// очень
const state = {
  registrationFormValid: false,
  chatConnected: true,
}

Группировка

Еще один способ упростить понимание состояния – группировать свойства, относящиеся к одному процессу. Например, одна форма может оперировать десятком различных свойств. Если на странице таких форм несколько, то объект состояния быстро распухнет и будет тяжело понять, что к чему относится даже с хорошим именованием.

// не очень
const state = {
  registrationFormValid: false,
  registrationFormErrors: [],
  registrationFormNameValue: '',
}

// очень
const state = {
  registrationForm: {
    valid: false,
    errors: [],
    fields: {
      name: '',
    },
  },
};

Визуальная структура

Частая ошибка при формировании состояния — привязка структуры состояния к визуальному оформлению. Проблема такой структуры в том, что если поменяется дизайн (даже небольшое расположение элементов), то объект состояния перестанет отражать реальность и его придется править.

// Не очень
const state = {
  centralBlock: {
    valid: true,
  },
  sideBar: {
    formValue: 'value'
  },
};

// Очень
const state = {
  registrationForm: {
    valid: false,
  },
  searchForm: {
    value: 'value'
  },
};

Хранение DOM-элементов

Состояние приложения — это данные, а не их отображение. Поэтому хранение DOM-элементов в состоянии считается плохой практикой. Фактически это возврат к структуре до введения состояния, когда данные хранились внутри представления.


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

  1. Именование в программировании

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

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

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

Об обучении на Хекслете

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 21 марта
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 21 марта

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

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

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

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