1 added
1 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Современные фронтенд-приложения включают множество элементов, которые должны корректно реагировать на изменения данных: спиннеры крутятся, кнопки отключаются, данные отправляются. Управление этим процессом требует продуманного подхода. В идеале, любые изменения в интерфейсе являются следствием изменения данных, то есть состояния приложения. Представьте себе форму регистрации, у которой кнопка отправки (submit) заблокирована во время выполнения запроса на сервер (с точки зрения UX это обязательно для любых форм). В таком случае состояние может приобрести следующий вид:</p>
1
<p>Современные фронтенд-приложения включают множество элементов, которые должны корректно реагировать на изменения данных: спиннеры крутятся, кнопки отключаются, данные отправляются. Управление этим процессом требует продуманного подхода. В идеале, любые изменения в интерфейсе являются следствием изменения данных, то есть состояния приложения. Представьте себе форму регистрации, у которой кнопка отправки (submit) заблокирована во время выполнения запроса на сервер (с точки зрения UX это обязательно для любых форм). В таком случае состояние может приобрести следующий вид:</p>
2
<p>Флаг submitDisabled отвечает за то, будет ли кнопка заблокирована. Она блокируется во время отправки формы и разблокируется если пришел ответ с ошибками для возможности повторной отправки. Если отправка прошла успешно, то вместо формы покажется что-то еще и этот флаг перестанет использоваться до появления новой формы.</p>
2
<p>Флаг submitDisabled отвечает за то, будет ли кнопка заблокирована. Она блокируется во время отправки формы и разблокируется если пришел ответ с ошибками для возможности повторной отправки. Если отправка прошла успешно, то вместо формы покажется что-то еще и этот флаг перестанет использоваться до появления новой формы.</p>
3
-
<p>В реальных приложениях все еще сложнее. Во время отправки данных блокируется не только кнопка отправки, но и поле для ввода. Более того, отправка данных в одном месте, может повлиять и на остальные блоки на странице, которые могут пропадать, блокироваться или видоизменяться. Не говоря уже о том, что причин блокировки кнопки может быть несколько. Она может быть заблокирована просто потому, что в форму введены некорректные данные.</p>
3
+
<p>В реальных приложениях все еще сложнее. Во время отправки данных блокируется не только кнопка отправки, но и поле для ввода. Более того, отправка данных в одном месте, может повлиять и на остальные блоки на странице, которые могут пропадать, блокироваться или видоизменяться. Не говоря уже о том, что причин блокировки кнопки может бы��ь несколько. Она может быть заблокирована просто потому, что в форму введены некорректные данные.</p>
4
<p>Если решать эту задачу в лоб, получится состояние с большим количеством флагов, где каждый флаг отвечает за какой-то свой элемент на странице.</p>
4
<p>Если решать эту задачу в лоб, получится состояние с большим количеством флагов, где каждый флаг отвечает за какой-то свой элемент на странице.</p>
5
<p>Но такой подход очень быстро усложняет разработку. Когда состояние описывается через множество независимых флагов, может возникнуть логическая путаница. Например, если форма валидна (valid: true), но submitDisabled: true, пользователь не сможет отправить ее, хотя логически это должно быть возможно. При увеличении числа таких флагов усложняется понимание взаимосвязей и проверок.</p>
5
<p>Но такой подход очень быстро усложняет разработку. Когда состояние описывается через множество независимых флагов, может возникнуть логическая путаница. Например, если форма валидна (valid: true), но submitDisabled: true, пользователь не сможет отправить ее, хотя логически это должно быть возможно. При увеличении числа таких флагов усложняется понимание взаимосвязей и проверок.</p>
6
<p>Также усложнится логика вывода, поскольку внешний вывод начнет зависеть от различных комбинаций флагов. Становится сложнее поддерживать и предсказывать, какие элементы интерфейса должны отображаться в разных состояниях. Разберем несколько примеров.</p>
6
<p>Также усложнится логика вывода, поскольку внешний вывод начнет зависеть от различных комбинаций флагов. Становится сложнее поддерживать и предсказывать, какие элементы интерфейса должны отображаться в разных состояниях. Разберем несколько примеров.</p>
7
<p>Проблема данного подхода в том, что он опирается не на причины происходящего, а на их следствия. Изменение активности кнопки, блокирование элементов, отображение спиннеров - все это следствия каких-то процессов. Умение выделить эти процессы и правильно описать в состоянии, один из краеугольных камней хорошей архитектуры.</p>
7
<p>Проблема данного подхода в том, что он опирается не на причины происходящего, а на их следствия. Изменение активности кнопки, блокирование элементов, отображение спиннеров - все это следствия каких-то процессов. Умение выделить эти процессы и правильно описать в состоянии, один из краеугольных камней хорошей архитектуры.</p>
8
<p>В примере выше большая часть флагов связана с процессом обработки данных формы. Предположим, что после отправки формы, данные уходят на сервер, затем от него приходит ответ и дальше результат отображается пользователю. Результат может быть как успешным, так и не успешным. Мы должны продумывать все исходы. Весь процесс условно можно разбить на несколько промежуточных состояний:</p>
8
<p>В примере выше большая часть флагов связана с процессом обработки данных формы. Предположим, что после отправки формы, данные уходят на сервер, затем от него приходит ответ и дальше результат отображается пользователю. Результат может быть как успешным, так и не успешным. Мы должны продумывать все исходы. Весь процесс условно можно разбить на несколько промежуточных состояний:</p>
9
<p><em>Предложенный набор не является универсальным. Процессы могут быть устроены сложнее, а значит потребуется другой набор состояний.</em></p>
9
<p><em>Предложенный набор не является универсальным. Процессы могут быть устроены сложнее, а значит потребуется другой набор состояний.</em></p>
10
<ul><li><em>filling</em>- заполнение формы. В этом состоянии все активно и доступно для редактирования.</li>
10
<ul><li><em>filling</em>- заполнение формы. В этом состоянии все активно и доступно для редактирования.</li>
11
<li><em>processing</em>(или<em>sending</em>) - отправка формы. Это то самое состояние, когда пользователь ждет, а приложение пытается предотвратить нежелательные действия, например, клики или изменения данных формы.</li>
11
<li><em>processing</em>(или<em>sending</em>) - отправка формы. Это то самое состояние, когда пользователь ждет, а приложение пытается предотвратить нежелательные действия, например, клики или изменения данных формы.</li>
12
<li><em>processed</em>(или<em>finished</em>) - состояние, обозначающее, что все завершилось. В нем форма уже не отображается.</li>
12
<li><em>processed</em>(или<em>finished</em>) - состояние, обозначающее, что все завершилось. В нем форма уже не отображается.</li>
13
<li><em>failed</em>- состояние, обозначающее завершение с ошибкой. Например, произошел сбой в сети во время загрузки или загруженные данные оказались неверными.</li>
13
<li><em>failed</em>- состояние, обозначающее завершение с ошибкой. Например, произошел сбой в сети во время загрузки или загруженные данные оказались неверными.</li>
14
</ul><p>Использование одного свойства state вместо нескольких флагов упрощает логику. Например, состояние processing автоматически определяет, что форма заблокирована, спиннер активен, а кнопка submit отключена - и для этого не нужно держать три отдельных флага. Такой подход снижает вероятность багов и делает код более читаемым.</p>
14
</ul><p>Использование одного свойства state вместо нескольких флагов упрощает логику. Например, состояние processing автоматически определяет, что форма заблокирована, спиннер активен, а кнопка submit отключена - и для этого не нужно держать три отдельных флага. Такой подход снижает вероятность багов и делает код более читаемым.</p>
15
<p>Перепишем наше состояние убрав оттуда все флаги и введя одно свойство отвечающее за состояние работы с формой:</p>
15
<p>Перепишем наше состояние убрав оттуда все флаги и введя одно свойство отвечающее за состояние работы с формой:</p>
16
<p>В состоянии мы выделили процесс регистрации, который может принимать одно из возможных состояний. Даже такое, на первый взгляд, небольшое изменение резко упрощает систему. Теперь нам не нужно отслеживать каждый участвующий в этом процессе элемент. Главное, чтобы все возможные состояния описывали все возможные варианты поведения. Тогда все проверки в выводе сведутся к проверке общего состояния:</p>
16
<p>В состоянии мы выделили процесс регистрации, который может принимать одно из возможных состояний. Даже такое, на первый взгляд, небольшое изменение резко упрощает систему. Теперь нам не нужно отслеживать каждый участвующий в этом процессе элемент. Главное, чтобы все возможные состояния описывали все возможные варианты поведения. Тогда все проверки в выводе сведутся к проверке общего состояния:</p>
17
<p>Кроме таких состояний, есть различные данные, сопровождающие наш процесс. Например,<em>processed</em>может завершиться с ошибками. В таком случае можно ввести дополнительно массив (или объект, в зависимости от структуры) с ошибками, который будет заполняться при их наличии:</p>
17
<p>Кроме таких состояний, есть различные данные, сопровождающие наш процесс. Например,<em>processed</em>может завершиться с ошибками. В таком случае можно ввести дополнительно массив (или объект, в зависимости от структуры) с ошибками, который будет заполняться при их наличии:</p>
18
<p>Причем этот же массив с ошибками удобно использовать для валидации формы до отправки на сервер. То есть будучи в состоянии<em>filling</em>.</p>
18
<p>Причем этот же массив с ошибками удобно использовать для валидации формы до отправки на сервер. То есть будучи в состоянии<em>filling</em>.</p>
19
<p>А что, если мы захотим блокировать возможность отправки формы до того момента, пока не пройдет валидация на фронтенде? Есть два подхода: либо мы проверяем, что<em>errors</em>пуст, либо, что лучше, мы вводим явное состояние валидности формы. И тогда состояние нашего приложения становится таким:</p>
19
<p>А что, если мы захотим блокировать возможность отправки формы до того момента, пока не пройдет валидация на фронтенде? Есть два подхода: либо мы проверяем, что<em>errors</em>пуст, либо, что лучше, мы вводим явное состояние валидности формы. И тогда состояние нашего приложения становится таким:</p>
20
<p>В некоторых ситуациях возможно объединение, когда процесс валидации соединен с процессом обработки самой регистрации. Тогда вместо отдельного состояния validationState, появится дополнительное состояние invalid внутри state. Это не совсем корректно с точки зрения моделирования (потому что у нас действительно два разных процесса), но иногда такой способ позволяет написать чуть более простой код (до тех пор пока различий не станет много).</p>
20
<p>В некоторых ситуациях возможно объединение, когда процесс валидации соединен с процессом обработки самой регистрации. Тогда вместо отдельного состояния validationState, появится дополнительное состояние invalid внутри state. Это не совсем корректно с точки зрения моделирования (потому что у нас действительно два разных процесса), но иногда такой способ позволяет написать чуть более простой код (до тех пор пока различий не станет много).</p>
21
<p>Глобально, такой подход в разработке называется программированием с явным выделенным состоянием. Он сводится к тому, что в рамках приложения находятся базовые процессы, от которых зависит все остальное. Причем не важно, какие инструменты используются для разработки: чистый DOM, jQuery или любой мощный современный фреймворк. Он применим везде и везде нужен.</p>
21
<p>Глобально, такой подход в разработке называется программированием с явным выделенным состоянием. Он сводится к тому, что в рамках приложения находятся базовые процессы, от которых зависит все остальное. Причем не важно, какие инструменты используются для разработки: чистый DOM, jQuery или любой мощный современный фреймворк. Он применим везде и везде нужен.</p>
22
<h2>Собирая все вместе</h2>
22
<h2>Собирая все вместе</h2>
23
<p>Пример ниже демонстрирует этот подход на простой форме регистрации.</p>
23
<p>Пример ниже демонстрирует этот подход на простой форме регистрации.</p>
24
<p><a>Попрактиковаться</a></p>
24
<p><a>Попрактиковаться</a></p>
25
<h3>Разбор кода</h3>
25
<h3>Разбор кода</h3>
26
<ul><li><p>Явное состояние state.registrationProcess.state</p>
26
<ul><li><p>Явное состояние state.registrationProcess.state</p>
27
<ul><li>filling - ввод данных в поле.</li>
27
<ul><li>filling - ввод данных в поле.</li>
28
<li>processing - отправка формы, блокировка кнопки и поля.</li>
28
<li>processing - отправка формы, блокировка кнопки и поля.</li>
29
<li>success - успешная регистрация, кнопка отключается.</li>
29
<li>success - успешная регистрация, кнопка отключается.</li>
30
<li>failed - ошибка валидации, отображается сообщение.</li>
30
<li>failed - ошибка валидации, отображается сообщение.</li>
31
</ul></li>
31
</ul></li>
32
<li><p>Функция updateUI()</p>
32
<li><p>Функция updateUI()</p>
33
<ul><li>Управляет блокировкой кнопки и полем ввода.</li>
33
<ul><li>Управляет блокировкой кнопки и полем ввода.</li>
34
<li>Показывает сообщения "Отправка...", "Ошибка..." или "Успешно отправлено!".</li>
34
<li>Показывает сообщения "Отправка...", "Ошибка..." или "Успешно отправлено!".</li>
35
<li>Блокирует кнопку, если валидация не пройдена.</li>
35
<li>Блокирует кнопку, если валидация не пройдена.</li>
36
</ul></li>
36
</ul></li>
37
<li><p>Валидация email при вводе</p>
37
<li><p>Валидация email при вводе</p>
38
<ul><li>Если email пуст, кнопка Submit остается заблокированной.</li>
38
<ul><li>Если email пуст, кнопка Submit остается заблокированной.</li>
39
<li>Ошибка отображается сразу, без необходимости отправки.</li>
39
<li>Ошибка отображается сразу, без необходимости отправки.</li>
40
</ul></li>
40
</ul></li>
41
<li><p>Отправка формы (submit обработчик)</p>
41
<li><p>Отправка формы (submit обработчик)</p>
42
<ul><li>При клике кнопка блокируется, состояние "processing".</li>
42
<ul><li>При клике кнопка блокируется, состояние "processing".</li>
43
<li>Через setTimeout() эмулируется серверный ответ.</li>
43
<li>Через setTimeout() эмулируется серверный ответ.</li>
44
<li>Если email валиден → success, иначе → failed.</li>
44
<li>Если email валиден → success, иначе → failed.</li>
45
</ul></li>
45
</ul></li>
46
</ul><p>Это невероятно мощная парадигма программирования, которая описана в книге "Автоматное Программирование" в<a>наших рекомендациях</a>.</p>
46
</ul><p>Это невероятно мощная парадигма программирования, которая описана в книге "Автоматное Программирование" в<a>наших рекомендациях</a>.</p>