Тема конечных автоматов занимает центральную роль во фронтенд разработке. Интерактивные элементы всегда вовлечены в процессы, связанные с изменением состояний. Модальные окна бывают открытые и скрытые, кнопка нажата, отжата или заблокирована (например, во время ajax-запроса). Примеров бесконечное множество. Нередко эти автоматы зависят друг от друга, что порождает иерархию автоматов. Например, возможность взаимодействовать с элементом на экране может появляться только после нажатия кнопки "редактировать".

В Реакте работа с автоматами проста до безобразия и в большинстве случаев не требует использования специальных библиотек. Возьмем, к примеру, кнопку, которая отвечает за показ куска текста. Ее состояния можно описать так:

  • По умолчанию текст скрыт (состояние hidden)
  • Клик по кнопке отображает текст (состояние shown)
  • Повторный клик прячет текст (hidden)

В данном случае у кнопки два состояния, поэтому можно упростить задачу и использовать флаг как индикатор состояния. Назовем его как isShown.

Я очень не рекомендую так делать в бекенде, когда состояние хранится в базе. Цена изменения автомата слишком высока, поэтому, даже, в случае бинарной логики, лучше делать полноценный автомат с именованными состояниями. Другими словами для хранения состояния используйте не булево поле (с true/false), а текстовое поле, в котором будет содержаться полное название состояния. Например, если статья может находиться в двух состояниях, Опубликована или Не опубликована, то нужно делать не поле 'published: bool' со значениями true и false, а поле 'publishing_state' со значениями 'published' и 'unpublished'`

See the Pen js_react_fsm by Hexlet (@hexlet) on CodePen.

Большая часть кода в Реакте (во всем фронтенде) выглядит именно так, как в примере выше. События порождают изменения состояния в данных, на основе которых, в свою очередь, меняется представление. Количество конечных автоматов во фронтенд приложениях растет с астрономической скоростью, главное их видеть и выделять явно.

Структура состояния

Данные, с которыми работает Реакт, как правило, приходят из бекенда. И эти данные тоже участвуют в разных процессах и находятся в разных состояниях. Например, статья может быть опубликована, а может быть и нет. И в зависимости от того, в каком она состоянии, рисуется UI. И здесь начинается самое интересное. Конкретно состояние опубликованности статьи не является частью UI, но UI использует это состояние, а при изменениях оно синхронизируется на фронтенде и бекенде. Но в UI часто появляются состояния, которые отвечают исключительно за внешний вид, но не являются частью данных.

Если предположить, что данные, пришедшие с бекенда, внутри нашего объекта-состояния хранятся как список под ключем items, то возникает вопрос: куда записывать данные, отвечающие за состояние UI? То есть те самые стейты, которые появляются только при взаимодействии с пользователем и не используются на серверной стороне?

Поясню на примере. К нам приходит статья такой структуры: { id: 3, name: 'How to programm', state: 'published' }. Мы отправляем ее в items. А в UI есть возможность зайти в ее редактирование, и для этого используется флаг (состояние) isEditing, который существует только на экране. Вопрос: где хранить эту переменную?

Самый простой вариант: изменить саму статью внутри items, так, чтобы она имела такой вид: { id: 3, name: 'How to programm', state: 'published', isEditing: true }. Хотя, на первый взгляд, он и кажется разумным, проблем он привносит больше, чем пользы. В основном эти проблемы связаны с задачами синхронизации. Иногда бывает нужно отправить всю статью на сервер (после изменений), а иногда перечитать ее заново с бекенда. В такой ситуации нужно будет либо извлекать только нужные данные, либо постоянно делать мердж (слияние), чтобы не потерять состояние UI. Практика показала, что гораздо проще добавлять отдельный список исключительно под задачи хранения состояния UI. То есть в нашем стейте появится список под названием itemUIStates, и для нашей статьи в него добавится элемент { articleId: 3, isEditing: true }.