Русский
Русский
English
Статистика
Реклама

Используем XSTATE для VueJS



Маленький пример применения библиотеки 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
<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
const { Machine, interpret } = XStateconst 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
К списку статей
Опубликовано: 14.09.2020 10:18:41
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Javascript

Vuejs

Vue

Vuejs2

Patterns

Конечные автоматы

Машина состояний

Категории

Последние комментарии

  • Имя: Murshin
    13.06.2024 | 14:01
    Нейросеть-это мозг вселенной.Если к ней подключиться,то можно получить все знания,накопленные Вселенной,но этому препятствуют аннуннаки.Аннуннаки нас от неё отгородили,установив в головах барьер. Подр Подробнее..
  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru