[JavaScript, VueJS] Используем XSTATE для VueJS

Автор Сообщение
news_bot ®

Стаж: 6 лет 4 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
14-Сен-2020 11:30


Маленький пример применения библиотеки XState от David Khourshid для декларативного описания логики компонента VueJS 2. XState это очень развитая библиотека для создания и использования конечных автоматов (КА) на JS. Неплохое подспорье в трудном деле создания веб приложений.
Предистория
В моей прошлой статье кратко описано зачем нужны машины состояний (конечные автоматы) и приведена простенькая реализация для работы с Vue. В моем велосипеде были только состояния и декларация состояний выглядела так:
{
    idle: ['waitingConfirmation'],
    waitingConfirmation: ['idle','waitingData'],
    waitingData: ['dataReady', 'dataProblem'],
    dataReady: [‘idle’],
    dataProblem: ['idle']
}

По сути это было перечисление состояний и для каждого описан массив возможных состояний, в которые может перейти система. Приложение просто “говорит” машине состояний — хочу перейти в такое состояние, если это возможно машина переходит в нужное состояние.
Этот подход работает, но возникают неудобства. Например если кнопка в разном состоянии должна инициировать переход в разные состояния. Придется городить условия. Вместо декларативности получаем кашу.
Изучив теорию по роликам из Ютюба, стало понятно, что события нужны и важны. В голове родился такой вид декларации:
{
  idle: {
    GET: 'waitingConfirmation',
  },
  waitingConfirmation: {
    CANCEL: 'idle',
    CONFIRM: 'waitingData'
  },
  waitingData: {
    SUCCESS: 'dataReady',
    FAILURE: 'dataProblem'
  },
  dataReady: {
    REPEAT: 'idle'
  },
  dataProblem: {
    REPEAT: 'idle'
  }
}

А это уже очень напоминает то, как описывает состояния библиотека XState. Почитав внимательней доку, я решил убрать самодельный велосипед в сарай, и пересесть на фирменный.
VUE + XState
Установка очень простая, читайте доку, после установки включаем XState в компонент:
import {Machine, interpret} from ‘xstate’

Создаем машину на основе объекта-декларации:
const myMachine = Machine({
    id: 'myMachineID',
    context: {
      /* some data */
    },
    initial: 'idle',
    states: {
        idle: {
          on: {
            GET: 'waitingConfirmation',
          }
        },
        waitingConfirmation: {
          on: {
            CANCEL: 'idle',
            CONFIRM: 'waitingData'
          }
        },
        waitingData: {
          on: {
            SUCCESS: 'dataReady',
            FAILURE: 'dataProblem'
          },
        },
        dataReady: {
          on: {
            REPEAT: 'idle'
          }
        },
        dataProblem: {
          on: {
            REPEAT: 'idle'
          }
        }
    }
})

Понятно, что есть состояния ‘idle’, ‘’waitingConfirmation' … и есть события в верхнем регистре GET, CANCEL, CONFIRM ….
Сама по себе машина не работает, из нее надо создать сервис с помощью функции interpret. Ссылку на этот сервис разместим в наш state, а заодно и ссылку на текущее состояние current:
data: {
    toggleService: interpret(myMachine),
    current: myMachine.initialState,
}

Сервис надо стартануть — start(), а также указать, что при переходах состояния мы обновляем значение current:
mounted() {
    this.toggleService
        .onTransition(state => {
            this.current = state
         })
        .start();
    }

В методы добавляем функцию send, ее и используем для управления машиной — передачи ей событий:
methods: {
   send(event) {
      this.toggleService.send(event);
   },
  …
}

Ну а дальше все просто. Передавать событие просто вызовом:
this.send(‘SUCCESS’)

Узнать текущее состояние:
this.current.value

Проверить нахождение машины в определенном состоянии так:
this.current.matches(‘waitingData')

Cоберем все вместе:

Template

SPL
<div id="app">
  <h2>XState machine with Vue</h2>
  <div class="panel">
    <div v-if="current.matches('idle')">
      <button @click="send('GET')">
        <span>Get data</span>
      </button>
    </div>
    <div v-if="current.matches('waitingConfirmation')">
      <button @click="send('CANCEL')">
        <span>Cancel</span>
      </button>
      <button @click="getData">
        <span>Confirm get data</span>
      </button>
    </div>
    <div v-if="current.matches('waitingData')" class="blink_me">
      loading ...
    </div>
    <div v-if="current.matches('dataReady')">
      <div class='data-hoder'>
        {{ text }}
      </div>
      <div>
        <button @click="send('REPEAT')">
          <span>Back</span>
        </button>
      </div>
    </div>
    <div v-if="current.matches('dataProblem')">
      <div class='data-hoder'>
        Data error!
      </div>
      <div>
        <button @click="send('REPEAT')">
          <span>Back</span>
        </button>
      </div>
    </div>
  </div>
  <div class="state">
    Current state: <span class="state-value">{{ current.value }}</span>
  </div>
</div>


JS

SPL
const { Machine, interpret } = XState
const myMachine = Machine({
    id: 'myMachineID',
    context: {
      /* some data */
    },
    initial: 'idle',
    states: {
        idle: {
          on: {
            GET: 'waitingConfirmation',
          }
        },
        waitingConfirmation: {
          on: {
            CANCEL: 'idle',
            CONFIRM: 'waitingData'
          }
        },
        waitingData: {
          on: {
            SUCCESS: 'dataReady',
            FAILURE: 'dataProblem'
          },
        },
        dataReady: {
          on: {
            REPEAT: 'idle'
          }
        },
        dataProblem: {
          on: {
            REPEAT: 'idle'
          }
        }
    }
  })
new Vue({
  el: "#app",
  data: {
    text: '',
    toggleService: interpret(myMachine),
    current: myMachine.initialState,
  },
  computed: {
  },
  mounted() {
    this.toggleService
        .onTransition(state => {
          this.current = state
        })
        .start();
  },
  methods: {
    send(event) {
      this.toggleService.send(event);
    },
    getData() {
      this.send('CONFIRM')
      requestMock()
      .then((data) => {
        this.text = data.text
        this.send('SUCCESS')
      })
      .catch(() => this.send('FAILURE'))
    },
  }
})
function randomInteger(min, max) {
  let rand = min + Math.random() * (max + 1 - min)
  return Math.floor(rand);
}
function requestMock() {
  return new Promise((resolve, reject) => {
    const randomValue = randomInteger(1,2)
    if(randomValue === 2) {
      let data = { text: 'Data received!!!'}
      setTimeout(resolve, 3000, data)
    }
    else {
      setTimeout(reject, 3000)
    }
  })
}


Ну и конечно все это можно потрогать на jsfiddle.net
Visualizer
XState предоставляет замечательный инструмент — Visualizer. Можно посмотреть диаграмму именно вашей машины. И не только посмотреть но и пощелкать по событиям и осуществить переходы. Вот так выглядит наш пример:

Итог
XState отлично работает, вместе с VueJS. Это упрощает работу компонента, позволяет избавиться от лишнего кода. Главное — декларация машины позволяет быстро понять логику. Данный пример простой, но я уже пробовал и на более сложном примере для рабочего проекта. Полет нормальный.
В данной статье я использовал только самый базовый функционал библиотеки, так как мне его пока хватает, но библиотека содержит еще массу интересный возможностей:
  • Guarded transitions
  • Actions (entry, exit, transition)
  • Extended state (context)
  • Orthogonal (parallel) states
  • Hierarchical (nested) states
  • History

А есть еще аналогичные библиотеки, например Robot. Вот сравнение Comparing state machines: XState vs. Robot. Так что если вас заинтересовала тема, вам будет чем заняться.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_javascript, #_vuejs, #_vue, #_vuejs2, #_vuejs, #_patterns, #_konechnye_avtomaty (конечные автоматы), #_mashina_sostojanij (машина состояний), #_javascript, #_javascript, #_vuejs
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 21-Июн 11:08
Часовой пояс: UTC + 5