Все мы знаем, что программировать и писать код можно без глубокого понимания концепций языка или вообще программирования. Это, конечно, в большинстве случаев немного грустно, но в то же время это означает, что есть куда расти и это прекрасно. Вы же можете хорошо и безопасно для себя и других управлять автомобилем, не понимая как именно этот автомобиль работает внутри, правда?
На работе или во время учёбы мы можем выполнять огромное количество повседневной работы, не имея глубоких знаний, но если мы хотим пользоваться языком, на котором мы пишем, на максимум, то всё же надо копать глубже:) Именно этим мы сегодня и будем заниматься.
Сегодня мы немного позаигрываем с тем, что делает людям много головной боли обычно — со сложными вычислениями. Звучит интригующе, не правда ли?
Переходя поближе к теме, хочу сказать, что я лично не встречал много информации на эту тему. Из этого могу заключить, что знаем мы об автоматическом программировании явно недостаточно.
КОНЕЧНЫЕ АВТОМАТЫ
Начнём с того, что такое конечные автоматы (Finite State Machines). Конечный автомат — это математическая абстракция, которая используется для разработки алгоритмов. Про алгоритмы вы можете узнать немного больше из моей статьи «O(n) или сложность алгоритмов».
И из того, что написано выше, можно решить, что автоматное программирование — это техническая тема, которая больше подходит математикам, чем фронтенд-разработчикам.
Однако, как только вы начнёте замечать конечные автоматы в вашем коде, станет ясно, что они являются ключевым инструментом для создания динамических и взаимодействующих интерфейсов.
Состояние и его изменения — это то, что лежит в основе работы большинства фронтенд-приложений. От кнопок до модальных окон и сложных иерархий состояний — конечные автоматы обеспечивают чёткую и понятную структуру для управления этими процессами.
Описание работы конечного автомата — это обычно нечто типа:
- автомат считывает серию входов;
- когда он считывает вход, он переходит в другое состояние;
- каждое состояние определяет, в какое состояние переходить для данного входа.
И это звучит заведомо сложно, но на самом деле всё достаточно просто. Разберёмся подробнее.
ПРОЩЕ О КОНЕЧНЫХ АВТОМАТАХ
Конечные автоматы — это математическая модель, описывающая систему с конечным количеством состояний и чётко определёнными правилами перехода между ними. В программировании конечные автоматы используются для управления состояниями объектов, компонентов или систем в ответ на некоторые события.
У конечного автомата есть свои компоненты, вот основные из них:
- Состояние: это мы всё знаем что такое. Самый банальный пример состояния: кнопка может быть в состоянии «нажата» или «отпущена».
- Начальное состояние (initial state): состояние, в котором находится автомат в начале работы.
- Событие (event): триггеры, вызывающие изменение состояния. К примеру, нажатие кнопки.
- Переход (transition): определение, как событие переводит автомат из одного состояния в другое.
- Окончательное состояние (final state): опциональное состояние, указывающее на завершение работы автомата.
Согласитесь, пока всё звучит достаточно просто и понятно, да?
ПЕРЕХОД К АВТОМАТНОМУ ПРОГРАММИРОВАНИЮ
В этой статье мы рассмотрим, как конечные автоматы помогают упорядочивать состояния в React и вообще во фронтенде, какие подходы можно использовать для работы с состояниями, и почему чёткое управление состоянием — это путь к эффективному, масштабируемому и надёжному коду.
Тема конечных автоматов занимает центральное место во фронтенд-разработке. Интерактивные элементы всегда связаны с процессами, касающимися изменения состояний.
Модальные окна могут быть открыты или скрыты, кнопка может быть нажата, отпущена или заблокирована (например, во время AJAX-запроса). Примеров множество.
Нередко эти автоматы зависят друг от друга, что создаёт иерархию автоматов. К примеру, возможность взаимодействия с элементом на экране может появиться только по нажатию кнопки «редактировать».
В React работа с автоматами проста до смешного и в большинстве случаев не требует использования специальных библиотек. Возьмём, например, кнопку, отвечающую за показ текста. Её состояния можно описать так:
- по умолчанию текст скрыт (состояние hidden);
- клик по кнопке отображает текст (состояние shown);
- повторный клик скрывает текст (hidden).
В этом случае кнопка имеет два состояния, поэтому можно упростить задачу и использовать флажок в качестве индикатора состояния. Назовём его isShown. Использовать флажки с булевыми значениями очень не рекомендуется в бэкенде, когда состояние сохраняется в базе данных.
Цена смены автомата слишком высока (например, изменение типа колонки с boolean на string), поэтому даже в случае бинарной логики лучше использовать полноценный автомат с названными состояниями. Другими словами, для хранения состояния используйте не булевое поле (true/false), а текстовое поле, в котором будет храниться полное название состояния.
Например, если статья может находиться в двух состояниях («опубликованная» или «не опубликованная»), вместо поля published: bool со значениями true и false следует использовать поле publishing_state со значениями published и unpublished.
import React from "react";
class Component extends React.Component {
constructor(props) {
super(props);
this.state = { isShown: false };
}
toggleText = () => {
const { isShown } = this.state;
this.setState({ isShown: !isShown });
};
render() {
const { isShown } = this.state;
return (
<div>
<button onClick={this.toggleText} type="button">
{isShown ? 'hide' : 'show'}
</button>
{isShown && <p>🌟</p>}
</div>
);
}
}
export default Component;
Большинство кода в React (как и во всём фронтенде) выглядит именно так, как в примере выше. События порождают изменения состояния в данных, на основе которых меняется представление.
Количество конечных автоматов во фронтенд-приложениях растёт с астрономической скоростью, главное — их видеть и явно выделять.
СТРУКТУРА СОСТОЯНИЯ
Ни для кого не секрет, что данные, с которыми работает React, обычно поступают из бэкенда. И эти данные также участвуют в разных процессах и находятся в разных состояниях.
Например, статья может быть опубликована, а может и нет. И в зависимости от того, в каком она состоянии, формируется UI.
И здесь начинается самое интересное. Конкретно состояние опубликованности статьи не является частью UI, но UI использует это состояние и при изменении оно синхронизируется между фронтендом и бэкендом. Но в UI часто появляются состояния, отвечающие исключительно за внешний вид, но не являющиеся частью данных.
Здесь, конечно, можно немного запутаться, но дальше мы будем «распутывать»:)
Если предположить, что данные, полученные из бэкенда, внутри нашего объекта состояния сохраняются как список под ключом items, возникает вопрос: куда записывать данные, отвечающие за состояние UI?
То есть, те состояния, которые появляются только при взаимодействии с пользователем и не используются на серверной стороне?
Пример: из бэкенда приходит статья такой структуры:
{ "id": 3, "name": "How to program", "state": "published" }
Она попадает в items. А в UI есть возможность зайти в её редактирование, и для этого используется флажок (состояние) isEditing, существующий только на экране.
Вопрос: где хранить эту переменную?
Самый простой вариант: изменить саму статью внутри items, чтобы она выглядела так:
{ "id": 3, "name": "How to program", "state": "published", "isEditing": true }
Хотя, на первый взгляд, это кажется разумным, проблемы, которые создаёт это, превышают пользу. В основном эти проблемы связаны с задачами синхронизации.
Иногда нужно отправить всю статью на сервер (после изменений), а иногда «перечитать» её с бэкенда. В такой ситуации нужно либо изымать только нужные данные, либо постоянно делать «мердж» (объединение), чтобы не потерять состояние UI.
Практика показала, что гораздо проще добавлять отдельный список исключительно для задач хранения состояния UI. То есть в state появится список под названием itemUIStates, и для статьи в него добавляется элемент:
{ "articleId": 3, "isEditing": true }
УПРОЩЕНИЕ РАБОТЫ С КОНЕЧНЫМИ АВТОМАТАМИ
Сейчас одной из самых популярных библиотек для работы с конечными автоматами есть библиотека XState. Она помогает структурировать состояния вашего приложения, сделать их понятными, предсказуемыми и легко управляемыми. Больше о ней можно почитать здесь, и также есть отдельная версия для работы с React, что очень удобно.
Где используется XState?
- Пользовательские интерфейсы: управление состояниями модальных окон, форм, выпадающих меню и т. д.
- Оркестрация задач: сложные сценарии с последовательными, параллельными или зависимыми задачами.
- Игровая логика: работа с состояниями персонажей или объектов.
- Системы реального времени: чаты, интерактивные приложения, поточная обработка данных.
Пример интеграции XState с React:
import React from 'react';
import { useMachine } from '@xstate/react';
import { createMachine } from 'xstate';
const toggleMachine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: {
on: { TOGGLE: 'active' },
},
active: {
on: { TOGGLE: 'inactive' },
},
},
});
const ToggleButton = () => {
const [state, send] = useMachine(toggleMachine);
return (
<button onClick={() => send('TOGGLE')}>
{state.matches('inactive') ? 'Turn On' : 'Turn Off'}
</button>
);
};
export default ToggleButton;
ПРЕИМУЩЕСТВА:
- понятное и предполагаемое управление состояниями;
- масштабируемость для сложных систем;
- лёгкость тестирования и дебагинга;
- интеграция с современными фреймворками.
НЕДОСТАТКИ:
- обучение может занять время для начинающих;
- само собой, в некоторых простых случаях может быть «чрезмерной» для использования.
ВЫВОД
Итак, конечные автоматы — это не просто математическая абстракция, а полезный инструмент, который может значительно упростить вашу жизнь как фронтенд-разработчика.
Они помогают упорядочить логику, сделать код более понятным и повысить его устойчивость к ошибкам.
Так, как и любая концепция, конечные автоматы могут сначала показаться сложными (и они в принципе не самое простое, что может быть), но как только вы начнёте видеть их в своем коде, работа с ними станет такой же естественной, как нажатие кнопки.
И, честно говоря, это совсем не похоже на магию — это всего лишь хорошо структурированный подход к управлению состояниями.
Не бойтесь учиться новому. Возьмите автоматическое программирование на вооружение, попробуйте XState или хотя бы рассмотрите свои компоненты в контексте конечных автоматов. Результат, поверьте, не заставит долго ждать.
Ну, а если пока всё кажется сложным — это нормально. Ошибайтесь, экспериментируйте, играйте с кодом и автоматами, и когда-нибудь это точно сработает вам на руку.
А мы, как всегда, встретимся в этом же месте в то же время:)