Articles

Comparaison des machines à états : XState vs Robot

Gérer l’état dans React peut devenir encombrant à mesure que la logique de l’application devient de plus en plus complexe. Des bibliothèques tierces telles que Redux, Flux et MobX aident, mais même ces outils viennent avec leur propre surcharge.

Une machine à états, également appelée machine à états finis ou automate à états finis, est un modèle mathématique de calcul. C’est une machine abstraite avec un nombre fini d’états à un moment donné.

Dans ce guide, nous passerons en revue les similitudes, les différences, les avantages et les inconvénients de deux machines d’état – XState et Robot – et nous marcherons à travers la façon de les utiliser pour simplifier la gestion de l’état dans les applications React.

Pourquoi utiliser une machine d’état?

L’état est une partie importante de la plupart des applications frontales, en particulier dans React. Pensez à l’état comme une représentation de la partie d’une application qui change.

Considérez un composant qui récupère des données à partir d’une API.

const Todo = () => { const = useState(); const handleClick = () => { fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(todo => setData(data.push(todo)) .catch(error => console.error(error) ) } return( <div> <button onClick={handleClick}> Fetch Data </button> {data && data.map(todo => (<p key={todo.id}> {todo.title} <span> {todo.completed} </span></p>) )} </div> ); }

Dans cet exemple, les données sont notre état puisque c’est la partie de l’application qui change chaque fois qu’un événement se produit – dans ce cas, le clic d’un bouton. Le problème avec cette configuration est qu’elle peut devenir compliquée.

Que se passe-t-il pendant que l’utilisateur attend que l’enregistrement soit récupéré ou si une erreur se produit pendant la récupération ? Nous devons ajouter plus d’états pour gérer ces problèmes.

const Todo = () => { const = useState(false); const = useState(); const = useState(false); const handleClick = () => { setLoading(true); fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(todo => { setLoading(false); setData(data.push(todo)); }) .catch(error => { setLoading(false); setIsError(true); }) } return( <div> {loading && <p> Loading Data... </p>} <button onClick={handleClick}> Fetch Data </button> {data && data.map(todo => (<p key={todo.id}> {todo.title} <span> {todo.completed} </span></p>) )} {error && <p> An error occured. Try again.</p>} </div> ); } 

Si votre application est complexe, les choses peuvent rapidement devenir incontrôlables à mesure que de nouvelles fonctionnalités sont ajoutées, rendant votre code difficile à comprendre, à tester et à améliorer.

Les machines à états abordent ce problème unique différemment. Avec les machines à états, vous pouvez définir tous les états dans lesquels notre application peut se trouver, les transitions entre les états et les effets secondaires qui peuvent se produire. Cela vous aide à éviter une situation où l’application est dans un état impossible.

State Diagram

State Diagram

Notre application peut être dans les états suivants :

  1. ready – l’état initial lorsque l’application démarre
  2. loading – lorsqu’un événement se produit i.e un bouton est cliqué
  3. success – lorsque le chargement se résout
  4. error – lorsque le chargement est rejeté

L’application transite d’un état à un autre lorsqu’une action est déclenchée – c’est-à-dire, lorsqu’un utilisateur clique sur un bouton. Vous avez un meilleur contrôle de votre application lorsque vous pouvez anticiper tous les états possibles dans lesquels elle peut se trouver.

Que font XState et Robot ?

Selon sa documentation officielle, XState est une bibliothèque pour créer, interpréter et exécuter des machines à états finis et des diagrammes d’états, ainsi que pour gérer les invocations de ces machines en tant qu’acteurs. Elle a été créée par David Khourshid pour résoudre les problèmes d’état dans les interfaces utilisateur.

Robot est une bibliothèque légère, fonctionnelle et immuable créée par Mathew Philips pour construire des machines à états finis. Elle a été inspirée par XState, Statecharts, et le langage de programmation P.

Prérequis

Pour suivre ce tutoriel, vous aurez besoin :

  • Connaissance de JavaScript
  • Connaissance de React
  • yarn ou npm v5.2 ou supérieure
  • Node version 10 ou supérieure

Mise en route

Pour démontrer les similitudes et les différences entre XState et Robot, nous allons créer une application qui récupère des données à partir d’une API.

Ouvrir un terminal et initialiser une application React.

npx create-react-app state-machine

Cela crée une application React appelée State Machine.

Puis, créer un service pour récupérer les données de l’API.

cd src && touch fetchTodo.js

La commande ci-dessus crée un fichier appelé fetchTodo.js dans le répertoire src.

Ouvrir le fichier et saisir ce qui suit .

export const fetchTodo = () => { return fetch('https://jsonplaceholder.typicode.com/todos/1') .then((response) => response.json()) .then((todo) => todo);};

Basiquement, chaque fois que la fonction fetchTodo est appelée, elle renvoie les données récupérées à partir de l’API.

Installation

XState peut être installé en utilisant npm ou yarn ou en intégrant le script à travers un CDN.

Pour installer la bibliothèque en utilisant npm, ouvrez un terminal et exécutez :

npm install xstate @xstate/react

Cela installe la bibliothèque de base xstate et un paquet pour React appelé @xstate/react qui vous permet d’utiliser des crochets XState personnalisés dans les applications React.

Vous pouvez installer Robot en utilisant npm ou yarn, mais pas un CDN.

Pour installer Robot, lancez un terminal et exécutez la commande suivante.

npm install robot3 react-robot

Robot propose également un package pour utiliser des hooks personnalisés dans React appelé react-robot

Créer une machine

Avant de pouvoir utiliser une machine à états, vous devez d’abord la définir.

Dans le répertoire src, créez un fichier appelé xstateMachine.js. Copiez le code ci-dessous dans le fichier créé.

import { Machine, assign } from 'xstate';import { fetchTodo } from '../fetchTodo';export const xstateMachine = Machine({ id: 'clickButton', initial: 'ready', context: { todo: null, }, states: { ready: { on: { CLICK: 'loading', }, }, loading: { invoke: { id: 'fetch-todo', src: fetchTodo, onDone: { target: 'success', actions: assign({ todo: (context, event) => event.data, }), }, onError: 'error', }, }, success: { on: { CLICK: 'loading', }, }, error: { on: { CLICK: 'loading', }, }, },});

Les machines sont définies à l’aide de la fonction factory Machine(). La machine que nous avons définie dans le code ci-dessus est composée d’identifiants, d’états, de contextes, d’actions et de transitions. Les ID sont utilisés pour identifier les nœuds d’état.

Les états de la machine sont :

  • ready
  • loading
  • success
  • error

Le contexte est un état étendu qui est utilisé pour représenter des données quantitatives telles que des nombres, une chaîne arbitraire, des objets, etc. L’état initial de l’application est défini comme ready. Lorsqu’un bouton est cliqué, une transition se produit, faisant passer l’état de ready à loading.

Dans l’état loading, il y a une propriété invoke qui est responsable de la résolution ou du rejet d’une promesse. Chaque fois que la promesse fetchTodo est résolue, l’état loading transite vers l’état success et l’action assign met à jour le contexte avec le résultat obtenu de la promesse. Si elle est rejetée, elle passe à l’état error.

Créer une machine avec Robot est similaire, bien qu’avec quelques différences clés. Une différence majeure est que, puisque Robot est une bibliothèque fonctionnelle, la plupart des actions sont effectuées à l’aide de fonctions, contrairement à XState, qui utilise des objets d’option.

Créer un fichier appelé robotMachine.js dans votre répertoire src et coller ce qui suit.

 import { createMachine, invoke, reduce, state, transition } from 'robot3';import { fetchTodo } from '../fetchTodo';const context = () => ({ todo: {},});export const robotMachine = createMachine( { ready: state(transition('CLICK', 'loading')), loading: invoke( fetchTodo, transition( 'done', 'success', reduce((ctx, evt) => ({ ...ctx, todo: evt.data })) ), transition( 'error', 'error', reduce((ctx, ev) => ({ ...ctx, error: ev.error })) ) ), success: state(transition('CLICK', 'loading')), error: state(transition('CLICK', 'loading')), }, context);

Dans Robot, les machines sont créées à l’aide de la fonction createMachine, qui accepte un objet. Un état est défini à l’aide de la fonction state et peut accepter un transition comme paramètre.

Le passage d’un état à un autre se fait avec la fonction transition, qui accepte l’événement et l’état suivant comme paramètres. En option, la fonction reduce peut être ajoutée à la transition comme troisième paramètre. Les fonctions de réduction prennent une fonction de réduction comme paramètre, qui est utilisée pour mettre à jour le contexte.

Robot a également une fonction invoke, similaire à la propriété invoke dans XState. Lorsque l’application est dans l’état loading, la fonction invoke est appelée. La fonction invoke est une sorte d’état qui invoque une promesse et renvoie une fonction ou une autre machine. Si la fonction invoke résout la promesse, elle envoie un événement done. Si elle est rejetée, elle envoie un événement error.

Construction du composant

Maintenant que notre machine est prête, la prochaine étape est de construire un composant qui utilisera la machine.

Créer un fichier dans votre répertoire src pour le composant et coller ce qui suit .

import React from 'react';import { useMachine } from '@xstate/react';import { xstateMachine } from './stateMachine';function Todo() { const = useMachine(xstateMachine); const { todo } = current.context; return ( <div> <button onClick={() => send('CLICK')}>Fetch Todo XState</button> {current.matches('loading') && <p>loading...</p>} {current.matches('success') && ( <p key={todo.id}> {todo.title} <span> {todo.completed} </span> </p> )} {current.matches('error') && <p>An error occured</p>} </div> );}export default Todo;

Pour utiliser une machine, nous devons importer le hook useMachine du @xstate/react library ainsi que la machine que nous avons créée plus tôt.

Le hook useMachine est un React Hook qui interprète une machine. Il est responsable du démarrage d’un service à exécuter tout au long du cycle de vie d’un composant.

Le hook useMachine accepte une machine comme paramètre et renvoie un tableau. Le tableau contient l’état current et send, qui est une fonction qui envoie un événement au service créé par le hook useMachine.

L’état current est un objet qui contient l’état, le contexte et certaines fonctions utilitaires. Pour vérifier l’état actuel, utilisez la propriété matches, qui renvoie un booléen. Lorsqu’un utilisateur clique sur le bouton, il envoie un événement au service. Celui-ci vérifie alors l’état actuel de la machine et rend l’interface utilisateur appropriée en fonction de cet état.

L’approche de Robot pour construire des composants est similaire. Un composant construit avec Robot ressemblerait à ceci:

import React from 'react';import { useMachine } from 'react-robot';import { robotMachine } from './robotMachine';function Todo() { const = useMachine(robotMachine); const { todo } = current.context; return ( <div> <button onClick={() => send('CLICK')}>Fetch Todo Robot</button> {current.name === 'loading' && <p>loading...</p>} {current.name === 'success' && ( <p key={todo.id}> {todo.title} <span> {todo.completed} </span> </p> )} {current.name === 'error' && <p>An error occured</p>} </div> );}export default RobotTodo;

Robot a également un crochet useMachine auquel on peut accéder en important le react-robot library. La différence dans l’implémentation est dans la façon dont un état est comparé. Alors que XState utilise la propriété matches, qui est une fonction qui accepte la chaîne de caractères que nous essayons de comparer, Robot utilise la propriété name pour vérifier l’état actuel avant de comparer.

Conclusion

Les machines à états offrent un moyen beaucoup mieux organisé de gérer l’état dans les applications React et elles sont faciles à mettre à l’échelle par rapport aux autres alternatives. XState et Robot sont deux bibliothèques très populaires qui, bien que très similaires, abordent la manipulation des machines d’un point de vue très différent.

Le référentiel de ce tutoriel est disponible sur GitHub.

Pour plus d’informations, consultez les ressources suivantes.

  • Documentation Robot
  • Documentation XState
  • « Infinitely Better UIs with Finite Automata » (vidéo)

Visibilité complète dans les applications React de production

Le débogage des applications React peut être difficile, surtout lorsque les utilisateurs rencontrent des problèmes difficiles à reproduire. Si vous êtes intéressé par la surveillance et le suivi de l’état Redux, la remontée automatique des erreurs JavaScript et le suivi des requêtes réseau lentes et du temps de chargement des composants, essayez LogRocket. LogRocket Dashboard Free Trial Banner

LogRocket est comme un DVR pour les applications web, enregistrant littéralement tout ce qui se passe sur votre application React. Au lieu de deviner pourquoi les problèmes se produisent, vous pouvez agréger et rendre compte de l’état dans lequel se trouvait votre application lorsqu’un problème est survenu. LogRocket surveille également les performances de votre app, en établissant des rapports avec des métriques telles que la charge CPU du client, l’utilisation de la mémoire du client, et plus encore.

Le package middleware LogRocket Redux ajoute une couche supplémentaire de visibilité dans vos sessions utilisateur. LogRocket enregistre toutes les actions et l’état de vos magasins Redux.

Modernez la façon dont vous déboguez vos applications React – commencez à surveiller gratuitement.

.