Поводом для написания данной статьи явились мои многочисленные наблюдая за разработчиками, которые только начали переходить на React.js с других фреймворков или для которых React.js стал первым фреймворком в его профессиональной деятельности.

Основной вопрос, с которым сталкивается начинающие разработчики на React.js — это что такое state компонента и как его использовать. Многие разработчики в большинстве случаев не правильно его используют, храня в нему кучу ненужных данных, которые впоследствии влияют на вычисления и производительность.

Расскажу о конечном автомате, что это такое, какие реализации существуют и какое отношение конечный автомат имеет к ReactJS.

Хорошее и простое определение конечного автомата можно дать так:
Конечный автомат — это компьютерная программа, которая состоит из:
  • Событий, на которые реагирует программа;
  • Состояний, в которых программа пребывает между событиями;
  • Переходов между состояниями при реагировании на события;
  • Действий, выполняемых в процессе переходов;
  • Переменных, которые содержат значения, необходимые для выполнения действий между событиями.

Конечные автоматы обычно представляют в двух видах:
  1. Ориентированного графа, где точки это определенные состояния, а стрелки — направления переходов.
  2. Двумерной таблицей, столбцы — это события, а строки — состояния, а ячейки содержат действия и переходы.

Автомат состоит из события, к которому привязаны соответствующие обработчики, и состояния, в котором находится автомат.

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

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

Machina.JS


Machina.js — является одной из реализаций конечного автомата. Подробную документацию можно найти на сайте проекта.

Что из себя представляет machina.js:
  • Работает в браузере и на nodejs
  • поддерживает amd-модули
  • зависит от undersore или lodash
  • написано Jim Cowart, автора postal.js и monologue.js

Чтобы лучше понять, что такое конечный автомат, рассмотрим пример реализации автомата на machina.js (взял код с сайта проекта).

Много кода
var vehicleSignal = new machina.Fsm( {

    // the initialize method is called right after the FSM
    // instance is constructed, giving you a place for any
    // setup behavior, etc. It receives the same arguments
    // (options) as the constructor function.
    initialize: function( options ) {
        // your setup code goes here...
    },

    namespace: "vehicle-signal",

    // `initialState` tells machina what state to start the FSM in.
    // The default value is "uninitialized". Not providing
    // this value will throw an exception in v1.0+
    initialState: "uninitialized",

    // The states object's top level properties are the
    // states in which the FSM can exist. Each state object
    // contains input handlers for the different inputs
    // handled while in that state.
    states: {
        uninitialized: {
            // Input handlers are usually functions. They can
            // take arguments, too (even though this one doesn't)
            // The "*" handler is special (more on that in a bit)
            "*": function() {
                this.deferUntilTransition();
                // the `transition` method takes a target state (as a string)
                // and transitions to it. You should NEVER directly assign the
                // state property on an FSM. Also - while it's certainly OK to
                // call `transition` externally, you usually end up with the
                // cleanest approach if you endeavor to transition *internally*
                // and just pass input to the FSM.
                this.transition( "green" );
            }
        },
        green: {
            // _onEnter is a special handler that is invoked
            // immediately as the FSM transitions into the new state
            _onEnter: function() {
                this.timer = setTimeout( function() {
                    this.handle( "timeout" );
                }.bind( this ), 30000 );
                this.emit( "vehicles", { status: GREEN } );
            },
            // If all you need to do is transition to a new state
            // inside an input handler, you can provide the string
            // name of the state in place of the input handler function.
            timeout: "green-interruptible",
            pedestrianWaiting: function() {
                this.deferUntilTransition( "green-interruptible" );
            },
            // _onExit is a special handler that is invoked just before
            // the FSM leaves the current state and transitions to another
            _onExit: function() {
                clearTimeout( this.timer );
            }
        },
        "green-interruptible": {
            pedestrianWaiting: "yellow"
        },
        yellow: {
            _onEnter: function() {
                this.timer = setTimeout( function() {
                    this.handle( "timeout" );
                }.bind( this ), 5000 );
                // machina FSMs are event emitters. Here we're
                // emitting a custom event and data, etc.
                this.emit( "vehicles", { status: YELLOW } );
            },
            timeout: "red",
            _onExit: function() {
                clearTimeout( this.timer );
            }
        },
        red: {
            _onEnter: function() {
                this.timer = setTimeout( function() {
                    this.handle( "timeout" );
                }.bind( this ), 1000 );
                this.emit( "vehicles", { status: RED } );
            },
            _reset: "green",
            _onExit: function() {
                clearTimeout(this.timer);
            }
        }
    }

    // While you can call the FSM's `handle` method externally, it doesn't
    // make for a terribly expressive API. As a general rule, you wrap calls
    // to `handle` with more semantically meaningful method calls like these:
    reset: function() {
        this.handle( "_reset" );
    },

    pedestrianWaiting: function() {
        this.handle( "pedestrianWaiting" );
    }
} );

// Now, to use it:
// This call causes the FSM to transition from uninitialized -> green
// & queues up pedestrianWaiting input, which replays after the timeout
// causes a transition to green-interruptible....which immediately
// transitions to yellow since we have a pedestrian waiting. After the
// next timeout, we end up in "red".
vehicleSignal.pedestrianWaiting();
// Once the FSM is in the "red" state, we can reset it to "green" by calling:
vehicleSignal.reset();

Как видно по комментариям в коде, new machina.Fsm — создает экземпляр конечного автомата, в качестве аргумента принимает конфигурацию автомата со всеми его состояниями.

В текущем примере задано 5 состояний автомата, причем автомат может находиться только в одном из этих состояний uninitialized, green, green-interruptible, yellow или red.

Состояние представляет из себя объект, который содержит обработчики текущего состояния.

Таким образом конечный автомат это набор состояний, в каждом из которых он может находится только при выполнении определенных условий.

Подробнее можно почитать на сайте проекта, а также посмотреть презентацию и видео.

Можно посмотреть на примеры других реализаций конечного автомата:

Конечный автомат в React.js


Думаю, что с конечными автоматами стало уже понятнее, теперь рассмотрим, как они используются в React.js.

Из документации следует, что UI в React.js следует рассматривать конечный автомат.

Таким образом для обновления View необходимо обновить его State и после чего React сам произведет магию перерасчета состояния и перерисует UI.

Обновить состояние View можно используя метод setState({}, [callback]). Данный метод сливает новый state с текущим и после чего вызывается метод render компонента.

Так что должно содержаться в state компонента? Согласно философии React и тому, что компонент представляет из себя конечный автомат, становится очевидным, что в state надо класть только то, что непосредственно отвечает за состояние вашего UI и влияет на его представление, в зависимости от внешних и внутренних условий. Таким образом не надо класть в state компонента все подряд.

Философия React такова, что надо создавать больше stateless компонентов и как можно меньше state компонентов, а в сам state класть минимальный набор данных, которые влияют на представление UI, а для всего остального есть props и Store.

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


  1. toxicmt
    30.12.2015 21:23

    Вы прямо как с моего доклада писали) www.youtube.com/watch?v=SduoWEXqeAg&feature=youtu.be&t=4h53m26s


    1. ekubyshin
      30.12.2015 21:31
      +1

      круть, спасиб за ссылку ознакомлюсь


  1. YChebotaev
    30.12.2015 23:59

    Целая статья из за одной неправильно понятой строчки в документации?

    Реакт не поддерживает машину состояний, он делает ровно противоположное: на основе только лишь текущего состояния компонента он «магически» вычисляет разницу между предыдущим и текущим состояниями и применяет это разницу к DOM дереву. Вся магия заключается в том, что состояние — это не то, что было передано в setState и даже не то, что содержится в props, а результат выполнения функции render, которая всегда возвращает новое виртуальное дерево. Сравнением старого виртуального дерева с новым и получается набор элементарных трансформаций, который, собственно, и применяется к DOM дереву.
    А вот момент, когда именно будет вызван метод render определяется реактом, но всегда после вызова setState, либо React.render.


    1. ekubyshin
      31.12.2015 00:50

      ты точно понял о чем статья?


    1. ekubyshin
      31.12.2015 01:02

      нашел абзац, который вводит в заблуждение. Да, Реакт не реализует машину. Про автоматы и machina.js было описано, чтобы лучше понять как работать со стейтом. Под набором состояний имел ввиду несколько другое, что юзер определяет состояния вьюхи, в каких она может находиться и в стейт должен класть именно то, что определяет эти состояния, а не все проперти подряд, как это иногда происходит. Соответственно про предыдущий и текущий стейт подмечено верно. Понятное дело, что состояние — это не то, что было передано в setState, см, написал же что мерджатся стейты.

      Перефразирую этот абзац, т.к. он вводит в заблуждение.


      1. YChebotaev
        31.12.2015 07:30

        Дело не в том, что стейты мержатся или не межратся, это вообще не важно. setState в реакте нужен лишь для того, чтобы запланировать вызов функции render в будущем. И вот уже ее результат и рассматривается реактом как состояние.

        Посмотрите на ее код, из которого я удалил незначащие проверки аргументов:

        ReactComponent.prototype.setState = function(partialState, callback) {
          this.updater.enqueueSetState(this, partialState);
          if (callback) {
            this.updater.enqueueCallback(this, callback);
          }
        };
        


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


        1. ekubyshin
          31.12.2015 09:32

          Дело не в том, что стейты мержатся или не межратся, это вообще не важно. setState в реакте нужен лишь для того, чтобы запланировать вызов функции render в будущем. И вот уже ее результат и рассматривается реактом как состояние.


          так и написано, только в другой формулировке.

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


          1. YChebotaev
            31.12.2015 15:25

            Вот что пишете вы:
            Из документации следует, что UI в React.js следует рассматривать конечный автомат.

            И вот что в документации:
            React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent.
            Где вы тут увидели конечный автомат? И как у вас термин простое состояние (simple state) (прямая противоположность конечному автомату) превратилось в этот самый конечный автомат? Более того, доказать что компонент в реакте не является конечным автоматом чрезвычайно просто, достаточно представить себе виджет, который выводит текущее время на экран. Сколько у него состояний?

            Дальше вы пишете:
            Таким образом для обновления View необходимо обновить его State и после чего React сам произведет магию перерасчета состояния и перерисует UI.

            В корне неверное утверждение. Чтобы обновить представление компонента, не обязательно обновлять его state, это можно сделать и установив свойства. И делает это не сам по себе реакт, а программист явным образом. Проверяется легко: попробуйте установить this.state.foo = 2, и представление компонента перерисовано не будет.

            Все остальное верно, однако и вы не сами эти утверждения делали, а просто сослались на документацию и философию.

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

            Очень смешной выпад в сторону программиста о том, что он не знает что такое конечный автомат. Ну я не только читал про реакт, но и вычитывал его исходный код, и даже реализовывал его самостоятельно.

            а вообще напиши статью об этом и я с удовольствием ее прочитаю.

            А вот выпады в стиле «сперва добейся» — совсем вам не к лицу.


            1. ekubyshin
              31.12.2015 19:10

              ты крутой чувак, несомненно, умеешь читать код.

              пускай критика твоя остается, но я как автор высказал свое мнение, у тебя оно другое и думай, что тебе угодно.

              Но чувак, все что ты делаешь на хабре это пишешь критикующие комментарии, лучше бы написал статью и выразил свое мнение.


            1. ekubyshin
              31.12.2015 19:24

              В корне неверное утверждение. Чтобы обновить представление компонента, не обязательно обновлять его state, это можно сделать и установив свойства. И делает это не сам по себе реакт, а программист явным образом. Проверяется легко: попробуйте установить this.state.foo = 2, и представление компонента перерисовано не будет.


              Капитан очевидность, спасибо, но это знает думаю каждый, то что об этом не написал, значит не посчитал нужным об этом рассказывать.

              А вот выпады в стиле «сперва добейся» — совсем вам не к лицу.


              ты меня знать не знаешь, а уже делаешь выводы, что мне к лицу, а что мне не к лицу. Звучит так, что ты всем клейма любитель ставить.

              Ну я не только читал про реакт, но и вычитывал его исходный код, и даже реализовывал его самостоятельно.


              ну рад за тебя, что ты еще и код умеешь читать.

              Слушай, ну обздался я немного, выразив свое мнение, ну и что такого, смысл статьи не изменился от этого — надо юзать state правильно.

              А вот что сделал ты? ровным счетом ничего, лишь пишешь критикующие комментарии. Напиши статью, вырази свое мнение, все ее почитают, возможно кто-нибудь напишет комментарий или плюсанет — вот это я понимаю экшен, а то что делаешь ты это отстой и не больше.


        1. ekubyshin
          31.12.2015 09:47

          а вообще напиши статью об этом и я с удовольствием ее прочитаю.