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.
Onze applicatie kan zich in de volgende toestanden bevinden:
-
ready
– de begintoestand wanneer de applicatie wordt opgestart -
loading
– wanneer een gebeurtenis plaatsvindt, d.w.z.e een knop wordt aangeklikt -
success
– wanneer het laden wordt opgelost -
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 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.