Что такое конечный автомат — и почему спиннер крутится рядом с ошибкой
Ты делаешь кнопку «Отправить». Пишешь три переменные: isLoading, isError, isSuccess. Всё работает. Потом пользователь жмёт кнопку второй раз, пока первый запрос ещё летит. И на экране одновременно крутится спиннер, горит красная ошибка и висит зелёная галочка.
Код при этом без единой опечатки. Просто три флага разрешают картинки, которые в жизни невозможны. Лекарство называется конечный автомат — и он проще, чем звучит.
Что такое конечный автомат простыми словами
Конечный автомат (state machine) — это штука, которая в каждый момент находится ровно в одном состоянии из заранее известного списка. И переходит из одного в другое только по разрешённым стрелкам.
Самый честный пример — светофор. У него три состояния: красный, жёлтый, зелёный. Гореть одновременно красным и зелёным он не может. Из зелёного он не прыгает сразу в красный — только через жёлтый. Это и есть автомат: список состояний плюс правила переходов.
Слово «конечный» значит, что состояний конечное число и ты можешь их все перечислить. Не «какие-то флаги», а список на одну строчку.
Почему три флага — это восемь состояний
Вот где спрятан подвох. Каждая логическая переменная — это два варианта. Про них подробно в статье про логический тип. Три независимых флага дают 2 × 2 × 2 = 8 комбинаций.
А осмысленных состояний у отправки формы всего четыре:
- ждём — ничего ещё не отправляли;
- загружается — запрос в пути;
- успех — сервер ответил «ок»;
- ошибка — что-то пошло не так.
Оставшиеся четыре комбинации — мусор. isLoading и isError одновременно. isSuccess и isError одновременно. Ты их не планировал, но код их допускает. И рано или поздно какой-то порядок событий туда их и приведёт: забыл сбросить один флаг — получил «загрузку с ошибкой».
Такие состояния называют невозможными — их не должно быть, но программа может в них оказаться. Каждый новый флаг удваивает число комбинаций. Пять флагов — уже 32 варианта, из которых нужны, может, шесть.
Как это чинит один status
Вместо трёх флагов заведи одну переменную, которая хранит название состояния:
const [status, setStatus] = useState("idle"); // idle | loading | success | error
Теперь «загрузка с ошибкой» нельзя даже записать. У переменной одно значение — значит, и состояние одно. Это уже половина автомата.
Вторая половина — правила переходов. Опиши их прямо объектом:
const transitions = {
idle: { SUBMIT: "loading" },
loading: { OK: "success", FAIL: "error" },
success: { RESET: "idle" },
error: { SUBMIT: "loading", RESET: "idle" },
};
function send(event) {
setStatus((current) => transitions[current][event] ?? current);
}
Смотри, что получилось. В состоянии loading события SUBMIT в таблице нет. Значит, второе нажатие кнопки просто ничего не меняет. Двойная отправка заказа побеждена без единого if (!isLoading).
Это и есть главный выигрыш. Автомат не только запрещает невозможные состояния — он запрещает невозможные переходы. Ты один раз пишешь, что куда можно, а не размазываешь проверки по десяти обработчикам.
Где ты встретишь автоматы
Чаще, чем кажется. Само слово «состояние» в React — уже полшага к автомату.
- Формы и запросы. Ровно пример выше. Библиотеки вроде TanStack Query отдают тебе
statusсо значениямиpending,error,success— не набор флагов. - Многошаговые формы и онбординг. Шаг 1 → шаг 2 → оплата → готово. Назад можно, перепрыгнуть оплату нельзя.
- Плеер. Играет, пауза, буферизация, конец. Кнопка «пауза» в состоянии «конец» ничего не делает.
- Заказ в магазине. Создан → оплачен → отправлен → доставлен. Отменить можно до отправки, после — нельзя.
- Игры. Персонаж стоит, бежит, прыгает, падает. Прыгнуть в прыжке нельзя — это правило перехода.
Когда логика разрастается, есть готовые инструменты. XState — библиотека, где автомат описывается целиком, а потом его можно нарисовать схемой. Но для большинства экранов хватает одной строки статуса и маленькой таблицы переходов.
Как попросить ИИ не плодить флаги
Нейросети обожают флаги. Попросишь «добавь загрузку» — получишь isLoading. Попросишь «добавь ошибку» — получишь isError рядом. Через неделю их пять. Задай форму сразу:
добавь в форму загрузку и ошибкуПерепиши состояние формы как конечный автомат. Одна переменная status со значениями idle, loading, success, error — без отдельных флагов isLoading и isError. Опиши переходы объектом transitions: из loading можно только в success или error, повторный SUBMIT во время loading игнорируется. Кнопка неактивна в loading. Покажи только изменённые строки.Хороший ответ легко проверить: в коде нет ни одного места, где можно выставить два состояния сразу.
Конечный автомат — это только для сложных программ?
Нет. Даже кнопка «Отправить» с четырьмя состояниями выигрывает. Чем раньше ты заменишь флаги одной строкой статуса, тем меньше странных багов поймаешь потом.
Чем конечный автомат отличается от обычного switch?
switch решает, что нарисовать в текущем состоянии. Автомат ещё и решает, куда можно перейти из него. Обычно они живут вместе: таблица переходов меняет статус, а switch по статусу выбирает экран.
А если состояний правда много?
Тогда разбей их на вложенные: «оформление заказа» внутри себя имеет свои шаги. Это называется иерархический автомат, и именно его удобно описывать в XState. Но начинай с плоского списка — он покрывает большинство случаев.
Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.