HTML Diff
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>