Таймер работы и отдыха кажется простой задачей, пока не начинаешь хранить его состояние. Я сделал для себя Android-приложение на React Native + Expo и TypeScript. Оно делит рабочий день, например с 9:00 до 18:00, на отрезки работы и отдыха и напоминает о перерывах. Режимов пять: настройка, ожидание начала дня, работа, отдых и окончание дня. На его примере покажу три способа описания состояния, их сильные и слабые стороны. Полный код — на GitHub.
Каждый режим описан своим типом со своими полями и общей меткой state. В настройке нет ни конца дня, ни отдыха, а restType и restStartedAt есть только у отдыха.
После проверки state TypeScript точно знает, какие поля доступны, и ! с ?. не нужны.
«Отдых без времени начала» невозможно даже собрать.
Если при переходе забыть перенести поле, это будет ошибка компиляции, а не undefined в рантайме.
Цена — многословность: каждый переход заново собирает объект со всеми полями нового режима.
Способы применения
Флаги подходят, когда они независимы, например, звук и полноэкранный режим в плеере друг другу не мешают.
Поля status без объединения хватит, когда у режимов нет собственных данных.
Размеченное объединение нужно, когда у каждого режима свои поля.
XState или свой редьюсер
Для пяти плоских режимов хватает своего редьюсера без зависимостей. Если появятся вложенные или параллельные состояния, отложенные переходы или понадобится визуализация, стоит смотреть на XState.
Технически в приложении расширенный конечный автомат (EFSM). Кроме режимов, он хранит переменные: границы дня, начало текущего отрезка и счётчик рабочих отрезков. На переходах у него есть условия (начать работу можно, только если день ещё не закончился), а сами переходы обновляют переменные. Без переменных пришлось бы плодить состояния WORK_1, WORK_2…