Что включает в себя понятие состояние? Если коротко, то состояние — это данные нашего приложения в любой момент времени, например, открытые вкладки в редакторе или браузере. Их количество и содержимое меняются в зависимости от того, какие кнопки мы нажимаем и что пытаемся загрузить. В общем случае, любое визуальное изменение в приложении или на странице, это всегда изменение состояния и никак иначе. Невозможна ситуация, при которой на странице сайта меняется какая-то деталь, но состояние при этом остается тем же. Изменение представления возможно только на основе изменения состояния.
А как же CSS-анимации? Да, анимация в CSS не связана с нашим приложением, но внутри браузера это состояние есть, и оно меняется.
Отличным примером состояния служит форма. Представьте себе поле для ввода телефона, которое отслеживает ошибки при вводе и сразу их показывает. Если ошибок нет, то оно позволяет выполнить отправку формы, иначе кнопка заблокирована. Что в данном случае является состоянием? Во-первых, все данные, вводимые в форму. Во-вторых, состояние корректности (валидности) данных формы: «валидно» и «не валидно». На основе этого состояния определяется, обводить красной рамкой поле ввода или нет. Ну и сами ошибки, которые нужно выводить, тоже являются частью состояния.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Форма с явным состоянием</title>
</head>
<body>
<form>
<input type="text" class="phone" name="phone" value="" />
<div class="error-message"></div>
<input type="submit" class="submit" disabled value="Save" />
</form>
<script>
const state = {
registrationForm: {
value: ""
valid: null,
errors: [],
},
};
const input = document.querySelector(".phone");
const submit = document.querySelector(".submit");
const errorMessage = document.querySelector(".error-message");
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("Неправильный формат");
}
submit.disabled = !state.registrationForm.valid;
if (state.registrationForm.valid) {
input.style.border = null;
} else {
input.style.border = "thick solid red";
errorMessage.textContent = errors.join(", ");
// тут выводим ошибки
}
});
</script>
</body>
</html>
Попрактиковаться
Как видно из примера, состояние описывается обычным JS-объектом, который создается при старте приложения:
// Константа потому что объект состояния всегда остается тем же
const state = {
// Состояние формы выделено в отдельное свойство,
// так как форм на странице может быть много
registrationForm: {
value: "",
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";
}
});
В данном случае отображение никак не вынесено в отдельное место, оно просто следует сразу за изменением состояния. Но если обработчиков будет больше, то придется повторять код изменения внешнего вида в каждом из них. Поэтому следующим развитием идеи отделение будет вынос отрисовку UI в отдельную функцию. Этим мы займемся в следующем уроке.
Рекомендации по организации состояния
Что хранится в состоянии
В приложениях далеко не все данные могут изменяться с течением времени. К таким данным может относиться конфигурация, например таймауты, различные ключи доступа или настройки библиотек. Такие данные (можно сказать, что они статические) в состоянии не хранят. Достаточно иметь отдельные константы, созданные прямо по месту использования, либо импортируемые из специального модуля с настройками:
// Не очень
const state = {
httpTimeout: 1000,
apiKey: "my super secret hash",
};
// Очень
const httpTimeout = 1000;
const apiKey = "some data";
Объект состояния
Само состояние лучше всего организовывать в виде одной константы, содержащей объект. За таким объектом гораздо проще следить, чем за набором разных констант или переменных. Более того, как вы увидите позже, это позволяет реализовать удобное отслеживание изменений для реализации архитектуры MVC, к которой мы медленно подбираемся.
// Не очень
const state = {
formState: {
/* ... */
},
};
const posts = [];
let connected = false;
// Очень
const state = {
formState: {
/* ... */
},
posts: [],
connected: false,
};
Раздельное хранение тоже встречается, но этой темы мы коснемся когда будем изучать фреймворки. Прямо сейчас лучше все хранить в одном месте.
Декларативность
Хорошее состояние описывает что происходит, а не как оно используется.
// Не очень
const state = {
redBorder: false,
};
// Очень
const state = {
registrationForm: {
valid: 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",
},
};
Обновление состояния
Лучше обновлять состояние целиком, а не по отдельным полям.
// Не очень
state.registrationForm.valid = true;
state.registrationForm.errors = [];
// Очень
Object.assign(state.registrationForm, { valid: true, errors: [] });
Хранение DOM-элементов
Состояние приложения — это данные, а не их отображение. Поэтому хранение DOM-элементов в состоянии считается плохой практикой. Фактически это возврат к структуре до введения состояния, когда данные хранились внутри представления.
Итого
Состояние — это ядро любого приложения, от него зависят все изменения интерфейса.
- Хорошая практика — отделять логику обновления состояния от логики его отображения.
- Следует структурировать данные, избегая хранения лишних значений.
- Чистый и декларативный подход к состоянию делает код понятным и легко поддерживаемым.
Дополнительные материалы

Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.