Finite State Machine в мире Front End
Сегодня хотел бы поговорить о “Finite State Machine” в FrontEnd. По-моему мнению, сообщество крайне плохо осведомленно о данном подходе. Однако, данная абстракция идеально подходит для решения задач по управлению состоянием в разработке интерфейсов. Именно поэтому — целью серии является повышение осведомленности сообщества о “state machine”.
Мы познакомимся с таким понятием как “state machine”(конечный автомат), разберем пару примеров использования, реализуем примеры на xstate и запустим серию “Finite State Machines в Front-end”.
Зачем это всё нужно?
Давайте разберём следующий сценарий: необходим реализовать анимированное меню в хэдер веб-приложения. Меню может находиться в нескольких состояниях. Мы-то с вами тут программисты опытные, все понимаем, что нужно добавить флаг.
const [isOpened, setIsOpened] = useState(false);
Супер! Теперь мы знаем, когда наше меню открыто.
Вроде, работает… Но меню, то у нас анимированное. Значит нам нужо игнорировать нажатие пока выполняется анимация. Ну что же, давайте добавим ещё один флаг.
const [isOpened, setIsOpened] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
Через какое-то время поступает новая хотелка от заказчика — меню может быть полностью выключено и неактивно. Без проблем! Давайте добавим еще один флаг… И, всего-то через пару дней разработки, код меню уже пестрит двустрочными IF-ами типа вот такого:
if (enabled && opened && !animating && !selected && finishedTransition && !endOfTheWorld && ...) { ... }
Вот тут, то и возникает желание ввести состояние меню. В простом виде оно может выглядить как-то так:
const [menuState, setMenuState] = useState('closed'); // closed | opened | animating | disabled
Но есть небольшая проблемка. Данная конструкция не позволяет нам описать допустимые переходы и предотвратить их. Например, нельзя перейти из состояния “disabled” в “animating”. Было бы очень круто понимать из какого состояния и куда можно переходить. Ведь чем сложнее система, тем труднее её поддерживать. Здесь нам и приходят на помощь “finite-state machine”.
Что такое State Machine?
Конечный автомат(“finite-state machine”) - это паттерн помогающий решать проблему перехода системы из одного состояния в другое, где количество состояний конечно.
Еще немного теории:
Конечный автомат в теории алгоритмов — математическая абстракция, модель дискретного устройства, имеющего один вход, один выход и в каждый момент времени находящегося в одном состоянии из множества возможных. Является частным случаем абстрактного дискретного автомата, число возможных внутренних состояний которого конечно. 😬
Самый популярный и часто встречающийся пример — светофор.
В любой момент времени семафор светофора имеет определенное состояние. Как правило, он либо:
- горит зеленый свет, а два других света выключены
- горит красный свет, а два других выключены
- горит желтый свет, а два других выключены
В терминологии State Machines включение или выключение света называется выходом(output).
Каждый из этих трех сценариев называется состоянием(state). Дорожный семафор будет менять состояние, когда получит входной сигнал(input), обычно это просто фиксированный таймер, который определяет, сколько времени светофор должен гореть зеленым, желтым и красным.
Таймер в данном случае является входом системы. Некоторые семафоры имеют кнопку, которую может нажать пешеход, чтобы вызвать красный цвет для автомобилей, и это будет еще одним входом.
Конечные автоматы состояний
Наш автомат состояния светофора считается конечным, потому что у нас конечное число состояний.
Некоторые системы могут иметь бесконечное число состояний.
Например, модель мировой экосистемы или жизнь насекомого. Мы не можем определить ее в конечном числе состояний.
Но светофор? Это простой механизм, и у него 3 состояния, как мы говорили выше.
Есть еще много примеров конечных автоматов, которые мы могли бы использовать:
- торговый автомат
- турникет для входа в метро
- система отопления
- автоматизированная система метро
- система самоуправляемых автомобилей
- лифт
Но давайте вернемся к нашему примеру со светофором, который очень прост, и мы можем легко рассуждать о нем.
Моделирование машины состояний
Как мы говорили выше, у нас есть 3 состояния, которые можно назвать зеленым, красным, желтым.
У нас есть начальное состояние. Допустим, наш светофор, когда мы его перезапускаем, начинает работать в зеленом состоянии.
У нас есть таймер, который после 50 секунд зеленого состояния переводит семафор в желтое состояние. У нас есть 8 секунд желтого состояния, затем мы переходим в красное состояние и находимся там 32 секунды, потому что эта дорога второстепенная и заслуживает меньшего времени зеленого состояния. После этого семафор возвращается в зеленое состояние, и цикл продолжается бесконечно, пока не прекратится подача электроэнергии, и семафор сбрасывается, когда снова получает питание.
В общей сложности мы имеем цикл длительностью 90 секунд.
Вот как мы можем его смоделировать:
В нашем простом случае, при любом состоянии машины у нас есть только одно логическое состояние, в которое мы можем перейти, поэтому диаграмма, которую я сделал, очень, очень проста.
Но когда ваша система начинает становиться сложнее, очень полезно иметь такие диаграммы и анализ, потому что вы можете рассуждать о своем приложении гораздо проще, чем просто держать все в голове.
В мире фронтенда
На фронте мы постоянно сталкиваемся с подобного рода задачами. Работа с состояними — это хлеб с солью в мире интерфейсов.
Меньше слов, больше примеров.
Мы начнём с классического примера — network request. Почему именно запрос? Потому что это очень понятная, знакомая и простая система. Она может пребывать в одном из четырёх состояний:
- idle (начальное состояние)
- pending
- success
- fail
Любой network request инициализируется с состоянием idle, далее при отправке запроса система переходит в состояние pending. Из состояния pending система может перейти в одно из двух финальных: если получен ответ от сервера, то система переходит в sucсess, иначе в fail.
Теперь, давайте поглядим на более интересный пример: видео-плеер. Мы будет реализовывать базовый видео-плеер с минимальным набором функций: начать проигрывание, стоп, пауза и начать сначала. Сперва нам нужно смоделировать систему:
“stopped” — видео-плеер остановлен и дорожка сброшена в 0. Начальное состояние системы. При запуске плеера мы переходим в “stopped”.
“playback” — видео-плеер воспроизводит видео. Из данного состояния мы можем перейти в “stopped”, тем самым остановив воспроизведение и перейдя в начало видео. Также, можем приостановить воспроизведение перейдя в “paused”. Когда видео заканчивается, то мы падаем на вход событие “finish” и система переходит в состояние “completed”.
“completed” — видео-плеер закончил воспроизведение. Из этого состояние есть только один переход в “playback”. На вход падается “restart” и видео начинает воспроизведение с самого начала.
“paused” — видео-плеер на паузе. Из этого состояние мы можем продолжить воспроизведение. Передав “continue” на вход системе она совершит переход в “playback”.
Xstate
Самой популярный библиотекой на сегодняшний день является xstate. Многие разработчики отдают свое предпочтение именно xstate для управления состоянием в своих проектах.
Чуть ранее мы уже разбирали пример со светофором. Давайте теперь посмотрим на то, как бы он мог быть реализован с помощью xstate. Одно из преимуществ finite-state machines — это то, что код описывающий машину является self-descriptive и его очень просто читать.
import { createMachine } from 'xstate';
const lightMachine = createMachine({
id: 'light',
initial: 'green',
states: {
green: {
on: {
TIMER: 'yellow'
}
},
yellow: {
on: {
TIMER: 'red'
}
},
red: {
on: {
TIMER: 'green'
}
}
}
});
const currentState = 'green';
const nextState = lightMachine.transition(currentState, 'TIMER').value;
// => 'yellow'
Вы можете поиграться с данной “state-machine” по ссылке: stately viz
А теперь поглядим на xstate реализацию видео-плеера.
import { createMachine } from "xstate";const videoPlayerMachine = createMachine({
id: "videoPlayer",
initial: "stopped",
states: {
stopped: {
on: {
start: "playback" },
},
playback: {
on: {
pause: "paused",
stop: "stopped",
finish: "completed"
},
},
paused: {
on: {
continue: "playback"
},
},
completed: {
on: {
restart: "playback"
},
},
},
});const playbackState = videoPlayerMachine.transition("stopped", "start").value;
Вы можете поиграться с данной “state-machine” по ссылке: stately viz
В будущем мы посвятим ни одну статью xstate и прочим реализациям finite-state machine, который можно использовать в FrontEnd.
В заключение
Мы познакомились с таким понятием как ”finite-state machine”. Разобрали несколько примеров и реализовали их с помощью библиотеки xstate.
Этой статьей мы запускаем серию посвященную использованию “finite-state machine” для управления состоянием приложения и его компонентов в UI.
Материал на тему: