Как стать автором
Обновить

Комментарии 11

Да это круто!
Нужно посмотреть как это масштабируется.
Можно ли из машин построить цеха и заводы, как они будут согласовывать свои состояния, и т.д.
Но такой подход мне определённо нравится.
В реальности, фронтенд на конечных автоматах часто превращается в плохо читаемый ад.
Собственно по этому и нужно посмотреть как это масштабируется.
Пользуемся автоматами для управления частями отображения компонентов. Действительно, любой интерфейс можно описать конечным набором состояний, и у нас в контексте ангуляра это достаточно удобно.
Даже сделали себе небольшую библиотеку для TypeScript, которая позволяет декларативно описывать машины и их свойства — какие данные(payload) будут применены; наследовать состояния; ограничивать, в какие состояния можно переходить из текущего; навешивать коллбэки на входы-выходы из состояний. Правда её код еще есть куда совершенствовать, сделано быстро-быстро на коленке :)

Пример использования
import { IStateDeclaration, StateMachine } from 'tstate-machine';

class ButtonStateMachine extends StateMachine {
    // initial state
    text: string = 'do request';
    diisabled: boolean = false;

    // state declarations
    // From what state we inherit and in what states we can transit
    @StateMachine.extend(StateMachine.INTIAL, ['requestState'])
    mainState: IStateDeclaration<ButtonStateMachine> = {}; // no changes relative to parent(initial) state

    @StateMachine.extend('mainState', ['doneState'])
    requestState: IStateDeclaration<ButtonStateMachine> = {
        text: 'sending...',
        disabled: true
    };

    @StateMachine.extend('requestState')
    doneState: IStateDeclaration<ButtonStateMachine> = {
        text: 'done'
        // no change disabled - property inherited from requestState and has `false` value
    };

    // common but important actions

    // states in which one we can transit from initial
    @StateMachine.hide
    protected get $next(): Array<string> {
        return ['mainState'];
    }

    // remember initial state
    constructor() {
        super();
        this.rememberInitState();
    }
}

const machine = new TextStateMachine();
machine.transitTo('maintState');
machine.transitTo('requestState');
console.log(machine.text); // autocomplete works fine!

Больше двадцати лет назад я сделал CBT (Computer Based Traing) по конечным автоматам для одного немецкого Университета. С тех пор пытыюсь применять конечные автоматы в своих проектах. При смене фирмы или языка программирования надо один раз написать базовый класс, а потом его использовать. Могу подтвердить, что конечные автоматы замечательно работают не только в системах с акцентом на пользовательский интерфейс, но и системах со сложной прикладной логикой.
В больших проектах конечные автоматы, если их моделировать с помощью UML, являются также средством коммуникации и спецификации поведения системы.
Их знание и использование благотворно сказывается на мышлении разработчиков, имхо.
Пример из собственной практики. В одном очень большом и ответственном проекте существовало текстовое описание логики на пяти страницах, которую разработчики отобразили в код. Код изобиловал if else, switch и т.д. Тестеры постоянно находили ошибки. Их залатывали, и старые тесты начинали бастовать. Когда меня послали в это гиблое место, я отобразил текст спецификации в конечный автомат. Он оказался удивительно элегантным на вид и работал замечательно. Правда он был не так просто устроен, как рассмотренный в статье, а со стеком и вложенными состояниями.
Так что советую всем: если логика сложная- попытайтесь постоить конечный автомат!

Был очень классный доклад от товарища из Microsoft Research — https://www.youtube.com/watch?v=VU1NKX6Qkxc
Кроме того, он написал JS библиотечку для работы с конечными автоматами в реакт http://davidkpiano.github.io/xstate/docs/#/
После чего, Раин Флоренс записал видео с использованием ее — https://www.youtube.com/watch?v=MkdV2-U16tc&t=9s
Для xstate еще есть пример кода для визуализации графа — https://codepen.io/davidkpiano/details/ayWKJO

Чего мне не хватает в Redux, так этот способа остановки диспетчеризации действия, основанного на текущем состоянии приложения без загрязнения редьюсера условной логикой.

Можно FSM прикрутить поверх редьюсера, с помощью higher order reducer. Это позволит оставить всю структуру приложения, включая код редьюсеров, вообще без изменений. Мы просто добавляем декларативное описание FSM для конкретных редьюсеров.
Может выглядеть как-то так:


reducer.js
import ActionTypes from '../action-types'
import withFsm from 'withFsm'

const initialState = {
    data: []
}

function reducer(state = initialState, action) {
    switch (action.type) {
        case ActionTypes.SUCCESS: return {...state, data: action.payload}
    }
}

// до этого момента был исходный код редьюсера без изменений
// теперь опишем для него FSM

const fsm = {
    name: 'idle',
    transitions: {
        'idle': {
            [ActionTypes.CLICK]: 'fetching'
        },
        'fetching': {
            [ActionTypes.SUCCESS]: 'idle',
            [ActionTypes.ERROR]: 'error',
        },
        'error': {
            [ActionTypes.RETRY]: 'fetching',
            [ActionTypes.CANCEL]: 'idle',
        }
    }
}

export default withFsm(reducer, fsm)

А сам код редьюсера высшего порядке withFsm примерно такой:


withFsm.js
export default function withFsm(reducer, fsm) {
    return function reducerWithFsm(state, action) {
        const reducedState = reducer(state, action)
        const transitions = fsm.transitions[state._fsmName]

        // недопустимый переход - состояние не меняем!
        if (!transitions || !transitions[action.type]) return state

        // допустимый переход - возвращаем новое состояние
        return {
            ...reducedState,
            _fsmName: transitions[action.type]
        }
    }
}

Вроде бы, получаем все плюсы от FSM плюс все плюсы от использования Redux.


Что скажете?

Мне не так давно пришла в голову очень похожая идея, вот только я сделал это немного по другому, на основе RTK (redux toolkit).

создал issue на githab в RTK:https://github.com/reduxjs/redux-toolkit/issues/1065

Очень давно использую этот подход в разных технологиях, и везде успешно. В середине 2000-х я даже пытался книгу об этом написать. С тех пор подходы существенно эволюционировали, но в основе та же суть.

Взялся что-то написать на эту тему, и в процессе прошелся по чужому опыту. Оказалось полезно, уже написано много разного, но и мне есть чего добавить от себя.

Спасибо за статью!

Мой подход заключается в том, что приложение описывается как дерево состояний в формате JSON. На его основе формируется техническое задание, генерируются шаблоны классов состояний с JDOC, и в runtime формируется дерево состояний.
Это же дерево или его ветки в процессе работы приложения могут сериализоваться и десериализоваться для организации команд CTRL+Z и CTRL+Y и других целей.

В итоге образуется целый ряд преимуществ, но это не описать в комментарии.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий