Articles

Zustandsautomaten im Vergleich: XState vs. Robot

Die Verwaltung von Zuständen in React kann mühsam werden, wenn die Anwendungslogik immer komplexer wird. Bibliotheken von Drittanbietern wie Redux, Flux und MobX helfen dabei, aber auch diese Tools haben ihren eigenen Overhead.

Ein Zustandsautomat, auch endlicher Zustandsautomat oder endliche Zustandsautomaten genannt, ist ein mathematisches Modell für Berechnungen. Es ist eine abstrakte Maschine mit einer endlichen Anzahl von Zuständen zu einem bestimmten Zeitpunkt.

In diesem Leitfaden werden wir die Ähnlichkeiten, Unterschiede, Vor- und Nachteile von zwei Zustandsautomaten – XState und Robot – besprechen und durchgehen, wie man sie verwendet, um die Zustandsverwaltung in React-Anwendungen zu vereinfachen.

Warum einen Zustandsautomaten verwenden?

Zustände sind ein wichtiger Bestandteil der meisten Frontend-Anwendungen, insbesondere in React. Stellen Sie sich den Zustand als eine Darstellung des Teils einer Anwendung vor, der sich ändert.

Betrachten Sie eine Komponente, die Daten von einer API abruft.

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 diesem Beispiel sind die Daten unser Zustand, da sie der Teil der Anwendung sind, der sich ändert, wenn ein Ereignis eintritt – in diesem Fall der Klick auf eine Schaltfläche. Das Problem bei diesem Aufbau ist, dass er kompliziert werden kann.

Was passiert, während der Benutzer darauf wartet, dass der Datensatz abgerufen wird, oder wenn beim Abruf ein Fehler auftritt? Wir müssen weitere Zustände hinzufügen, um diese Probleme zu bewältigen.

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

Wenn Ihre Anwendung komplex ist, können die Dinge schnell außer Kontrolle geraten, wenn neue Funktionen hinzugefügt werden, was es schwierig macht, Ihren Code zu verstehen, zu testen und zu verbessern.

Zustandsautomaten gehen dieses einzigartige Problem anders an. Mit Zustandsautomaten können Sie alle Zustände definieren, in denen sich unsere Anwendung befinden kann, die Übergänge zwischen den Zuständen und die möglichen Nebeneffekte. Auf diese Weise lässt sich vermeiden, dass sich die Anwendung in einem unmöglichen Zustand befindet.

State Diagram

State Diagram

Unsere Anwendung kann sich in den folgenden Zuständen befinden:

  1. ready – der Anfangszustand, wenn die Anwendung gestartet wird
  2. loading – wenn ein Ereignis eintritt, d. h. eine Schaltfläche angeklickt wird.d. h. eine Schaltfläche wird angeklickt
  3. success – wenn das Laden aufgelöst wird
  4. error – wenn das Laden abgelehnt wird

Die Anwendung geht von einem Zustand in einen anderen über, wenn eine Aktion ausgelöst wird – d. h., wenn ein Benutzer auf eine Schaltfläche klickt. Sie haben eine bessere Kontrolle über Ihre Anwendung, wenn Sie alle möglichen Zustände vorhersehen können, in denen sie sich befinden kann.

Was machen XState und Robot?

Nach der offiziellen Dokumentation ist XState eine Bibliothek zum Erstellen, Interpretieren und Ausführen von endlichen Zustandsmaschinen und Zustandsdiagrammen sowie zum Verwalten von Aufrufen dieser Maschinen als Akteure. Sie wurde von David Khourshid entwickelt, um die Probleme von Zuständen in Benutzeroberflächen zu lösen.

Robot ist eine leichtgewichtige, funktionale und unveränderliche Bibliothek, die von Mathew Philips zur Erstellung von endlichen Zustandsautomaten entwickelt wurde. Sie wurde von XState, Statecharts und der Programmiersprache P inspiriert.

Voraussetzungen

Um diesem Tutorial zu folgen, benötigen Sie:

  • Kenntnisse in JavaScript
  • Kenntnisse in React
  • yarn oder npm v5.2 oder höher
  • Node Version 10 oder höher

Einstieg

Um die Ähnlichkeiten und Unterschiede zwischen XState und Robot zu demonstrieren, werden wir eine Anwendung erstellen, die Daten von einer API abruft.

Öffnen Sie ein Terminal und initialisieren Sie eine React-Anwendung.

npx create-react-app state-machine

Dadurch wird eine React-Anwendung mit dem Namen State Machine erstellt.

Als Nächstes erstellen Sie einen Dienst, der die Daten von der API abruft.

cd src && touch fetchTodo.js

Der obige Befehl erstellt eine Datei namens fetchTodo.js im Verzeichnis src.

Öffnen Sie die Datei und geben Sie Folgendes ein.

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

Grundsätzlich werden bei jedem Aufruf der Funktion fetchTodo die von der API abgerufenen Daten zurückgegeben.

Installation

XState kann mit npm oder yarn installiert oder das Skript über ein CDN eingebettet werden.

Um die Bibliothek mit npm zu installieren, öffnen Sie ein Terminal und führen Sie aus:

npm install xstate @xstate/react

Dies installiert die xstate Kernbibliothek und ein Paket für React namens @xstate/react, das es ermöglicht, benutzerdefinierte XState-Hooks in React-Anwendungen zu verwenden.

Sie können Robot mit npm oder yarn installieren, aber nicht über ein CDN.

Um Robot zu installieren, starten Sie ein Terminal und führen Sie den folgenden Befehl aus.

npm install robot3 react-robot

Robot bietet auch ein Paket zur Verwendung von benutzerdefinierten Hooks in React namens react-robot

Erstellen eines Automaten

Bevor Sie einen Zustandsautomaten verwenden können, müssen Sie ihn zunächst definieren.

Erstellen Sie im Verzeichnis src eine Datei namens xstateMachine.js. Kopieren Sie den nachstehenden Code in die erstellte Datei.

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

Maschinen werden mit der Funktion Machine() factory definiert. Die Maschine, die wir im obigen Code definiert haben, besteht aus IDs, Zuständen, Kontexten, Aktionen und Übergängen. IDs werden verwendet, um die Zustandsknoten zu identifizieren.

Die Zustände in der Maschine sind:

  • ready
  • loading
  • success
  • error

Kontext ist ein erweiterter Zustand, der verwendet wird, um quantitative Daten wie Zahlen, beliebige Zeichenketten, Objekte, etc. darzustellen. Der Anfangszustand der Anwendung ist als ready definiert. Wenn eine Schaltfläche angeklickt wird, findet ein Übergang statt, der den Zustand von ready nach loading verschiebt.

Im Zustand loading gibt es eine invoke-Eigenschaft, die dafür verantwortlich ist, ein Versprechen entweder aufzulösen oder abzulehnen. Wenn das fetchTodo-Versprechen aufgelöst wird, geht der loading-Zustand in den success-Zustand über und die assign-Aktion aktualisiert den Kontext mit dem Ergebnis des Versprechens. Wird sie abgelehnt, geht sie in den Zustand error über.

Die Erstellung einer Maschine mit Robot ist ähnlich, wenn auch mit einigen wichtigen Unterschieden. Ein Hauptunterschied besteht darin, dass Robot eine funktionale Bibliothek ist und die meisten Aktionen mit Funktionen ausgeführt werden, im Gegensatz zu XState, das Optionsobjekte verwendet.

Erstelle eine Datei namens robotMachine.js in deinem src-Verzeichnis und füge Folgendes ein.

 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 werden Maschinen mit der Funktion createMachine erstellt, die ein Objekt akzeptiert. Ein Zustand wird mit der Funktion state definiert und kann einen transition als Parameter annehmen.

Der Wechsel von einem Zustand in einen anderen erfolgt mit der Funktion transition, die das Ereignis und den nächsten Zustand als Parameter annimmt. Optional kann die reduce-Funktion der transition als dritter Parameter hinzugefügt werden. Reduce-Funktionen nehmen eine Reducer-Funktion als Parameter an, die zur Aktualisierung des Kontexts verwendet wird.

Robot verfügt auch über eine invoke-Funktion, ähnlich der invoke-Eigenschaft in XState. Wenn sich die Anwendung im Zustand loading befindet, wird die Funktion invoke aufgerufen. Die invoke-Funktion ist eine Art Zustand, der ein Versprechen aufruft und eine Funktion oder eine andere Maschine zurückgibt. Wenn die Funktion invoke das Versprechen auflöst, sendet sie ein Ereignis done. Wenn es abgelehnt wird, sendet sie ein error-Ereignis.

Erstellung der Komponente

Nun, da unsere Maschine bereit ist, besteht der nächste Schritt darin, eine Komponente zu erstellen, die die Maschine nutzen wird.

Erstelle eine Datei in deinem src-Verzeichnis für die Komponente und füge das Folgende ein.

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;

Um eine Maschine zu verwenden, müssen wir den useMachine-Hook aus dem @xstate/react library importieren, ebenso wie die Maschine, die wir zuvor erstellt haben.

Der useMachine-Hook ist ein React-Hook, der eine Maschine interpretiert. Er ist dafür verantwortlich, einen Dienst zu starten, der während des Lebenszyklus einer Komponente läuft.

Der useMachine-Hook akzeptiert eine Maschine als Parameter und gibt ein Array zurück. Das Array enthält den currentZustand und send, eine Funktion, die ein Ereignis an den vom useMachine-Hook erstellten Dienst sendet.

Der currentZustand ist ein Objekt, das den Zustand, den Kontext und einige Hilfsfunktionen enthält. Um den aktuellen Zustand zu überprüfen, verwenden Sie die matches-Eigenschaft, die einen booleschen Wert zurückgibt. Wenn ein Benutzer auf die Schaltfläche klickt, sendet er ein Ereignis an den Dienst. Dieser prüft dann den aktuellen Zustand der Maschine und rendert die entsprechende Benutzeroberfläche auf der Grundlage des Zustands.

Der Robot-Ansatz zur Erstellung von Komponenten ist ähnlich. Eine mit Robot erstellte Komponente würde wie folgt aussehen:

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 hat auch einen useMachine-Hook, auf den durch Importieren des react-robot library zugegriffen werden kann. Der Unterschied in der Implementierung liegt in der Art und Weise, wie ein Zustand verglichen wird. Während XState die matches-Eigenschaft verwendet, die eine Funktion ist, die den zu vergleichenden String akzeptiert, verwendet Robot die name-Eigenschaft, um den aktuellen Zustand vor dem Vergleich zu überprüfen.

Fazit

State Machines bieten eine viel besser organisierte Möglichkeit, den Zustand in React-Anwendungen zu verwalten, und sie sind im Vergleich zu anderen Alternativen einfach zu skalieren. XState und Robot sind zwei sehr populäre Bibliotheken, die sich zwar sehr ähneln, aber den Umgang mit Automaten aus sehr unterschiedlichen Blickwinkeln angehen.

Das Repository für dieses Tutorial ist auf GitHub verfügbar.

Weitere Informationen finden Sie in den folgenden Ressourcen.

  • Robot-Dokumentation
  • XState-Dokumentation
  • „Infinitely Better UIs with Finite Automata“ (Video)

Vollständige Transparenz in produktiven React-Apps

Das Debuggen von React-Anwendungen kann schwierig sein, vor allem wenn Benutzer Probleme haben, die schwer zu reproduzieren sind. Wenn Sie daran interessiert sind, den Redux-Status zu überwachen und zu verfolgen, JavaScript-Fehler automatisch aufzudecken und langsame Netzwerkanfragen und Komponentenladezeiten zu verfolgen, sollten Sie LogRocket ausprobieren. LogRocket Dashboard Free Trial Banner

LogRocket ist wie ein DVR für Web-Apps und zeichnet buchstäblich alles auf, was in Ihrer React-App passiert. Anstatt zu raten, warum Probleme auftreten, können Sie zusammenfassen und berichten, in welchem Zustand sich Ihre Anwendung befand, als ein Problem auftrat. LogRocket überwacht auch die Leistung Ihrer App und liefert Metriken wie die CPU-Last des Clients, die Nutzung des Arbeitsspeichers und mehr.

Das LogRocket Redux Middleware-Paket bietet eine zusätzliche Ebene der Transparenz Ihrer Benutzersitzungen. LogRocket protokolliert alle Aktionen und Zustände aus Ihren Redux-Speicher.

Modernisieren Sie die Art und Weise, wie Sie Ihre React-Anwendungen debuggen – beginnen Sie mit der kostenlosen Überwachung.