Un petit exemple d'utilisation de la bibliothèque XState de David Khourshid pour décrire de manière déclarative la logique d'un composant VueJS 2. XState est une bibliothèque très avancée pour créer et utiliser des machines d'état dans JS. Pas une mauvaise aide dans la tâche difficile de créer des applications Web.
Préhistoire
Dans mon dernier article, j'ai brièvement décrit pourquoi les machines à états (machines à états) sont nécessaires et une implémentation simple pour travailler avec Vue. Mon vélo n'avait que des états et la déclaration d'état ressemblait à ceci:
{
idle: ['waitingConfirmation'],
waitingConfirmation: ['idle','waitingData'],
waitingData: ['dataReady', 'dataProblem'],
dataReady: [‘idle’],
dataProblem: ['idle']
}
En fait, c'était une énumération d'états, et pour chacun, un tableau d'états possibles vers lesquels le système peut aller a été décrit. L'application "dit" simplement à la machine à états - je veux entrer dans un tel état, si possible, la machine entre dans l'état souhaité.
Cette approche fonctionne, mais il y a des inconvénients. Par exemple, si un bouton dans un état différent doit lancer une transition vers différents états. Nous devrons clôturer les conditions. Au lieu d'être déclaratif, nous obtenons un gâchis.
Après avoir étudié la théorie sur les vidéos de YouTube, il est devenu clair que les événements sont nécessaires et importants. Ce genre de déclaration est né dans ma tête:
{
idle: {
GET: 'waitingConfirmation',
},
waitingConfirmation: {
CANCEL: 'idle',
CONFIRM: 'waitingData'
},
waitingData: {
SUCCESS: 'dataReady',
FAILURE: 'dataProblem'
},
dataReady: {
REPEAT: 'idle'
},
dataProblem: {
REPEAT: 'idle'
}
}
Et c'est déjà très similaire à la façon dont la bibliothèque XState décrit les états. Après avoir lu le quai plus attentivement, j'ai décidé de mettre mon vélo fait maison dans la grange et de passer à un vélo de marque.
VUE + XState
L'installation est très simple, lisez la doc, après l'installation nous incluons XState dans le composant:
import {Machine, interpret} from ‘xstate’
Nous créons une voiture basée sur l'objet de déclaration:
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'
}
}
}
})
Il est clair qu'il y a des états «idle», «waitConfirmation»… et il y a des événements en majuscules GET, CANCEL, CONFIRM….
La machine elle-même ne fonctionne pas, vous devez créer un service à partir de celle-ci en utilisant la fonction d'interprétation. Nous placerons un lien vers ce service dans notre état, et en même temps un lien vers l'état actuel:
data: {
toggleService: interpret(myMachine),
current: myMachine.initialState,
}
Le service doit être démarré - start (), et également indiquer que lorsque l'état passe, nous mettons à jour la valeur de current:
mounted() {
this.toggleService
.onTransition(state => {
this.current = state
})
.start();
}
Nous ajoutons la fonction d'envoi aux méthodes et l'utilisons pour contrôler la machine - pour lui envoyer des événements:
methods: {
send(event) {
this.toggleService.send(event);
},
…
}
Eh bien, tout est simple. Envoyez un événement simplement en appelant:
this.send(‘SUCCESS’)
Découvrez l'état actuel:
this.current.value
Vérifiez si la machine est dans un certain état comme suit:
this.current.matches(‘waitingData')
Mettre tous ensemble:
Modèle
<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)
}
})
}
Et bien sûr, tout cela peut être abordé sur jsfiddle.net
Visualiseur
XState fournit un excellent outil, le Visualizer . Vous pouvez voir le diagramme de votre voiture particulière. Et pas seulement pour regarder mais aussi pour cliquer sur les événements et faire des transitions. Voici à quoi ressemble notre exemple:
Résultat
XState fonctionne très bien avec VueJS. Cela simplifie le travail du composant et vous permet de vous débarrasser du code inutile. L'essentiel est que la déclaration de la machine vous permette de comprendre rapidement la logique. Cet exemple est simple, mais je l'ai déjà essayé sur un exemple plus complexe pour un projet de travail. Le vol est normal.
Dans cet article, je n'ai utilisé que les fonctionnalités les plus basiques de la bibliothèque, car j'en ai encore assez, mais la bibliothèque contient beaucoup plus de fonctionnalités intéressantes:
- Transitions surveillées
- Actions (entrée, sortie, transition)
- État étendu (contexte)
- États orthogonaux (parallèles)
- États hiérarchiques (imbriqués)
- Histoire
Et il existe également des bibliothèques similaires, par exemple Robot. Voici une comparaison de la comparaison des machines d'état: XState vs. Robot . Donc, si un sujet vous intéresse, vous aurez quelque chose à faire.