
Um pequeno exemplo de uso da biblioteca XState de David Khourshid para descrever declarativamente a lógica de um componente VueJS 2. XState é uma biblioteca muito avançada para criar e usar máquinas de estado em JS. Não é uma má ajuda na difícil tarefa de criar aplicativos da web.
Pré-história
Em meu último artigo, descrevi brevemente por que as máquinas de estado (máquinas de estado) são necessárias e uma implementação simples para trabalhar com o Vue. Minha bicicleta tinha apenas estados e a declaração de estado era assim:
{
idle: ['waitingConfirmation'],
waitingConfirmation: ['idle','waitingData'],
waitingData: ['dataReady', 'dataProblem'],
dataReady: [‘idle’],
dataProblem: ['idle']
}
Na verdade, era uma enumeração de estados e, para cada um, foi descrita uma série de possíveis estados para os quais o sistema pode ir. O aplicativo simplesmente "diz" para a máquina de estado - eu quero ir para esse estado, se possível, a máquina vai para o estado desejado.
Essa abordagem funciona, mas tem uma desvantagem. Por exemplo, se um botão em um estado diferente deve iniciar uma transição para estados diferentes. Teremos que cercar nas condições. Em vez de sermos declarativos, ficamos confusos.
Tendo estudado a teoria dos vídeos do YouTube, ficou claro que os eventos são necessários e importantes. Esse tipo de declaração nasceu na minha cabeça:
{
idle: {
GET: 'waitingConfirmation',
},
waitingConfirmation: {
CANCEL: 'idle',
CONFIRM: 'waitingData'
},
waitingData: {
SUCCESS: 'dataReady',
FAILURE: 'dataProblem'
},
dataReady: {
REPEAT: 'idle'
},
dataProblem: {
REPEAT: 'idle'
}
}
E isso já é muito semelhante a como a biblioteca XState descreve os estados. Depois de ler o cais com mais atenção, decidi colocar minha bicicleta feita em casa no celeiro e trocar por uma de marca.
VUE + XState
A instalação é muito simples, leia o documento, após a instalação incluímos o XState no componente:
import {Machine, interpret} from ‘xstate’
Criamos um carro com base no objeto de declaração:
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'
}
}
}
})
É claro que existem estados 'idle', '' waitingConfirmation '... e existem eventos em maiúsculas GET, CANCEL, CONFIRM….
A máquina em si não funciona; um serviço deve ser criado a partir dela usando a função interpretar. Colocaremos um link para este serviço em nosso estado, e ao mesmo tempo um link para o estado atual:
data: {
toggleService: interpret(myMachine),
current: myMachine.initialState,
}
O serviço deve ser iniciado - start (), e também indicar que quando o estado muda, atualizamos o valor de current:
mounted() {
this.toggleService
.onTransition(state => {
this.current = state
})
.start();
}
Adicionamos a função de envio aos métodos e a usamos para controlar a máquina - para enviar eventos a ela:
methods: {
send(event) {
this.toggleService.send(event);
},
…
}
Bem, então tudo é simples. Envie um evento simplesmente chamando:
this.send(‘SUCCESS’)
Descubra o estado atual:
this.current.value
Verifique se a máquina está em uma determinada condição da seguinte forma:
this.current.matches(‘waitingData')
Juntando tudo:
Modelo
<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 } = 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)
}
})
}
E, claro, tudo isso pode ser tocado em jsfiddle.net
Visualizar
O XState oferece uma ótima ferramenta, o Visualizer . Você pode ver o diagrama de seu carro particular. E não só para ver, mas também para clicar nos eventos e fazer transições. É assim que nosso exemplo se parece:

Resultado
XState funciona muito bem com VueJS. Isso simplifica o trabalho do componente e permite que você se livre de códigos desnecessários. O principal é que a declaração da máquina permite compreender rapidamente a lógica. Este exemplo é simples, mas eu já experimentei em um exemplo mais complexo para um projeto de trabalho. O vôo está normal.
Neste artigo, usei apenas a funcionalidade mais básica da biblioteca, já que ainda tenho o suficiente, mas a biblioteca contém muitos outros recursos interessantes:
- Transições protegidas
- Ações (entrada, saída, transição)
- Estado estendido (contexto)
- Estados ortogonais (paralelos)
- Estados hierárquicos (aninhados)
- História
E também existem bibliotecas semelhantes, por exemplo Robot. Aqui está uma comparação de máquinas de estado comparando: XState vs. Robot . Portanto, se você estiver interessado em um tópico, terá algo para fazer.