Articles

Vergelijking van toestandsmachines: XState vs. Robot

Het beheren van toestanden in React kan omslachtig worden naarmate de applicatielogica complexer wordt. Bibliotheken van derden, zoals Redux, Flux en MobX, helpen daarbij, maar zelfs deze tools hebben hun eigen overhead.

Een toestandsmachine, ook wel eindige toestandsmachine of eindige toestandsautomaat genoemd, is een wiskundig model voor berekeningen. Het is een abstracte machine met een eindig aantal toestanden op elk gegeven moment.

In deze gids bekijken we de overeenkomsten, verschillen, voor- en nadelen van twee state machines – XState en Robot – en lopen we door hoe je ze kunt gebruiken om het state management in React-toepassingen te vereenvoudigen.

Waarom een state machine gebruiken?

State is een belangrijk onderdeel van de meeste frontend-toepassingen, vooral in React. Denk aan state als een representatie van het deel van een applicatie dat verandert.

Overweeg een component dat gegevens ophaalt uit een 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> ); }

In dit voorbeeld is data onze state, omdat het het deel van de applicatie is dat verandert wanneer een gebeurtenis plaatsvindt – in dit geval, de klik van een knop. Het probleem met deze opzet is dat het ingewikkeld kan worden.

Wat gebeurt er terwijl de gebruiker wacht tot het record is opgehaald of als er een fout optreedt tijdens het ophalen? We moeten meer toestanden toevoegen om deze problemen op te lossen.

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> ); } 

Als uw toepassing complex is, kunnen dingen snel uit de hand lopen als nieuwe functies worden toegevoegd, waardoor uw code moeilijk te begrijpen, te testen en te verbeteren wordt.

State machines benaderen dit unieke probleem op een andere manier. Met toestandsmachines kun je alle toestanden definiëren waarin onze applicatie zich kan bevinden, de overgangen tussen de toestanden, en de neveneffecten die kunnen optreden. Dit helpt u een situatie te voorkomen waarin de applicatie zich in een onmogelijke toestand bevindt.

State Diagram

State Diagram

Onze applicatie kan zich in de volgende toestanden bevinden:

  1. ready – de begintoestand wanneer de applicatie wordt opgestart
  2. loading – wanneer een gebeurtenis plaatsvindt, d.w.z.e een knop wordt aangeklikt
  3. success – wanneer het laden wordt opgelost
  4. error – wanneer het laden wordt geweigerd

De app gaat van de ene toestand over naar de andere wanneer een actie wordt getriggerd – d.w.z, wanneer een gebruiker op een knop klikt. U hebt uw applicatie beter onder controle wanneer u kunt anticiperen op alle mogelijke toestanden waarin deze zich kan bevinden.

Wat doen XState en Robot?

Volgens de officiële documentatie is XState een bibliotheek voor het maken, interpreteren en uitvoeren van eindige toestandsmachines en toestandsdiagrammen, en voor het beheren van aanroepen van die machines als actoren. Het is gemaakt door David Khourshid om de problemen van toestanden in gebruikersinterfaces aan te pakken.

Robot is een lichtgewicht, functionele, en onveranderlijke bibliotheek gemaakt door Mathew Philips voor het bouwen van eindige toestandsmachines. Het is geïnspireerd door XState, Statecharts, en de programmeertaal P.

Voorwaarden

Om deze tutorial te kunnen volgen, heb je nodig:

  • Kennis van JavaScript
  • Kennis van React
  • yarn of npm v5.2 of hoger
  • Node versie 10 of hoger

Aan de slag

Om de overeenkomsten en verschillen tussen XState en Robot aan te tonen, gaan we een applicatie maken die gegevens ophaalt van een API.

Open een terminal en initialiseer een React-toepassing.

npx create-react-app state-machine

Dit creëert een React-toepassing genaamd State Machine.

Volgende, creëer een service om de gegevens op te halen uit de API.

cd src && touch fetchTodo.js

Het bovenstaande commando creëert een bestand genaamd fetchTodo.js in de src directory.

Open het bestand en voer het volgende in.

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

Basically, elke keer dat de fetchTodo functie wordt aangeroepen, retourneert het de gegevens opgehaald uit de API.

Installatie

XState kan worden geïnstalleerd door gebruik te maken van npm of yarn of door het script in te sluiten via een CDN.

Om de bibliotheek te installeren met behulp van npm, opent u een terminal en voert u uit:

npm install xstate @xstate/react

Dit installeert de xstate kernbibliotheek en een pakket voor React genaamd @xstate/react waarmee u aangepaste XState-haken in React-toepassingen kunt gebruiken.

U kunt Robot installeren met npm of yarn, maar niet met een CDN.

Om Robot te installeren, start u een terminal en voert u het volgende commando uit.

npm install robot3 react-robot

Robot biedt ook een pakket om aangepaste haken in React te gebruiken, react-robot

Een machine maken

Voordat u een toestandsmachine kunt gebruiken, moet u deze eerst definiëren.

In de map src maakt u een bestand met de naam xstateMachine.js. Kopieer de onderstaande code in het aangemaakte bestand.

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', }, }, },});

Machines worden gedefinieerd met behulp van de Machine() fabrieksfunctie. De machine die we in de bovenstaande code hebben gedefinieerd bestaat uit ID’s, toestanden, contexten, acties en overgangen. ID’s worden gebruikt om de toestandsknooppunten te identificeren.

De toestanden in de machine zijn:

  • ready
  • loading
  • success
  • error

Context is een uitgebreide toestand die wordt gebruikt om kwantitatieve gegevens weer te geven, zoals getallen, arbitraire tekenreeksen, objecten, enzovoort. De begintoestand van de toepassing is gedefinieerd als ready. Wanneer op een knop wordt geklikt, vindt een overgang plaats, waardoor de toestand van ready naar loading wordt verplaatst.

In de toestand loading is er een invoke eigenschap die verantwoordelijk is voor het resolven of verwerpen van een belofte. Wanneer de fetchTodo belofte wordt opgelost, gaat de loading toestand over naar de success toestand en de assign actie werkt de context bij met het resultaat van de belofte. Als het wordt afgewezen, gaat het naar de error toestand.

Het maken van een machine met Robot is vergelijkbaar, zij het met enkele belangrijke verschillen. Een groot verschil is dat, aangezien Robot een functionele bibliotheek is, de meeste acties worden uitgevoerd met functies, in tegenstelling tot XState, die optie-objecten gebruikt.

Creëer een bestand genaamd robotMachine.js in uw src directory en plak het volgende.

 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);

In Robot worden machines gemaakt met de createMachine functie, die een object accepteert. Een toestand wordt gedefinieerd met de state functie en kan een transition als parameter aanvaarden.

Vervangen van de ene toestand naar de andere gebeurt met de transition functie, die de gebeurtenis en de volgende toestand als parameters aanvaardt. Optioneel kan de reduce functie worden toegevoegd aan de transition als een derde parameter. Reduce functies nemen een reducer functie als parameter, die wordt gebruikt om de context bij te werken.

Robot heeft ook een invoke functie, vergelijkbaar met de invoke eigenschap in XState. Wanneer de toepassing zich in de loading-status bevindt, wordt de invoke-functie aangeroepen. De invoke functie is een soort toestand die een belofte aanroept en een functie of een andere machine retourneert. Als de invoke functie de belofte omzet, zal het een done event sturen. Als het wordt afgewezen, stuurt het een error event.

Bouwen van de component

Nu onze machine klaar is, is de volgende stap het bouwen van een component die de machine zal gebruiken.

Maak een bestand in uw src directory voor de component en plak het volgende.

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;

Om een machine te gebruiken moeten we de useMachine hook uit de @xstate/react library importeren, evenals de machine die we eerder hebben gemaakt.

De useMachine hook is een React Hook die een machine interpreteert. Het is verantwoordelijk voor het starten van een service om gedurende de levenscyclus van een component te draaien.

De useMachine haak accepteert een machine als een parameter en retourneert een array. De array bevat de current state en send, dat is een functie die een event stuurt naar de service die is aangemaakt door de useMachine hook.

De current state is een object dat de state, context, en enkele utility functies bevat. Om de huidige status te controleren, gebruikt u de matches eigenschap, die een boolean retourneert. Wanneer een gebruiker op de knop klikt, wordt er een event naar de service gestuurd. Deze controleert dan de huidige toestand van de machine en rendert de juiste UI op basis van de toestand.

De Robot benadering van het bouwen van componenten is vergelijkbaar. Een component gebouwd met Robot zou er als volgt uitzien:

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 heeft ook een useMachine haak die kan worden benaderd door de react-robot library te importeren. Het verschil in de implementatie zit in de manier waarop een toestand wordt vergeleken. Terwijl XState de matches eigenschap gebruikt, wat een functie is die de string accepteert die we proberen te vergelijken, gebruikt Robot de name eigenschap om de huidige staat te controleren alvorens te vergelijken.

Conclusie

State machines bieden een veel beter georganiseerde manier om staat te beheren in React applicaties en ze zijn gemakkelijk te schalen in vergelijking met andere alternatieven. XState en Robot zijn twee zeer populaire bibliotheken die, hoewel ze erg op elkaar lijken, het omgaan met machines vanuit zeer verschillende gezichtspunten benaderen.

De repository voor deze tutorial is beschikbaar op GitHub.

Voor meer informatie, bekijk de volgende bronnen.

  • Robot-documentatie
  • XState-documentatie
  • “Oneindig veel betere UI’s met eindige automaten” (video)

Volledig inzicht in productie React-apps

Het debuggen van React-applicaties kan lastig zijn, vooral wanneer gebruikers problemen ondervinden die moeilijk te reproduceren zijn. Als u geïnteresseerd bent in het bewaken en volgen van Redux-status, het automatisch aan het licht brengen van JavaScript-fouten en het volgen van trage netwerkverzoeken en laadtijden voor componenten, probeert u LogRocket. LogRocket Dashboard Free Trial Banner

LogRocket is als een DVR voor webapps, waarin letterlijk alles wordt vastgelegd wat er in je React-app gebeurt. U hoeft niet meer te gissen naar de oorzaak van problemen, maar u kunt de status van uw applicatie samenvoegen en rapporteren op het moment dat een probleem optrad. LogRocket monitort ook de prestaties van je app, rapporteert met statistieken zoals CPU belasting, client geheugengebruik, en meer.

Het LogRocket Redux middleware pakket voegt een extra laag van zichtbaarheid in uw gebruikerssessies. LogRocket logt alle acties en toestanden van uw Redux stores.

Moderniseer de manier waarop je je React-apps debugt – begin gratis met monitoring.