Finite-State-Machines mit Reagieren

0
20

Als JavaScript-Anwendungen auf die web mehr gewachsen komplexen, so hat auch die Komplexität im Umgang mit Staat, in dieser Anwendungen — Zustand wird die Summe aller Daten, die eine Anwendung braucht, um seine Funktion auszuführen. In den letzten Jahren, es hat eine Tonne von großer innovation im Bereich der staatlichen Verwaltung durch tools wie Redux, MobX, und Vuex. Etwas, das noch nicht bekommen ganz so viel Aufmerksamkeit, obwohl, ist state-design.

Was meine ich mit ” state-design?

Lassen Sie uns die Szene ein wenig. In der Vergangenheit, wenn Sie eine Anwendung erstellen, abrufen muss einige Daten aus einer back-End-Dienst und zeigt es dem Benutzer, den ich entworfen habe, meinen Zustand zu verwenden, boolean flags für verschiedene Dinge wie isLoading, isSuccess, isError, und so weiter bis die Linie. Wie diese Anzahl von boolean-flags wächst, obwohl die Anzahl der möglichen Zustände, die meine Bewerbung haben kann, wächst exponentiell mit it — deutlich erhöhen die Wahrscheinlichkeit, dass ein Benutzer Begegnung ein unbeabsichtigtes oder Fehler-Zustand.

Um dieses Problem zu lösen, ich habe in den letzten paar Monaten erforschen die Verwendung von finite-state-Maschinen als Weg zu einer besseren Gestaltung der Zustand meiner Anwendungen.

Endliche Automaten sind ein mathematisches Modell der Berechnung, ursprünglich entwickelt in den frühen 1940er-Jahren, verwendet worden für Jahrzehnte zu erstellen hardware und software für eine Breite Palette von Technologien.

Ein endlicher Automat kann definiert werden als eine abstrakte Maschine, die es in genau einem von einer endlichen Anzahl von Zuständen zu einem bestimmten Zeitpunkt. In der Praxis, wenn ein Staat die Maschine zeichnet sich durch eine Liste von Zuständen, wobei jeder Staat die Definition eines endlichen, deterministischen Staaten können umgestellt werden, um von einer gegebenen Aktion.

Aufgrund dieser endlichen und deterministischen Natur, können wir verwenden Zustandsdiagramme visualisieren unsere Anwendung — vor oder nach dem es gebaut ist.

Zum Beispiel, wenn wir wollten, zu visualisieren einen Authentifizierungs-workflow konnten wir drei übergreifende besagt, dass unsere Anwendung könnte für einen Benutzer: angemeldet, abgemeldet, oder laden.

Ein Zustandsdiagramm zeigt eine Anwendung von abgemeldet, zu laden, zu angemeldet.

State-machines, die aufgrund Ihrer Berechenbarkeit, sind besonders beliebt in Anwendungen, wo Zuverlässigkeit entscheidend ist — wie der Luftfahrt-software, die Herstellung und sogar der NASA Space Launch System. Sie haben sich ein Standbein in der Spiele-Entwicklung Gemeinschaft seit Jahrzehnten, wie gut.

In diesem Artikel werden wir angehen, etwas zu bauen, dass die meisten Anwendungen auf die web-Nutzung: – Authentifizierung. Wir verwenden die state-Diagramm oben, um uns zu führen.

Bevor wir beginnen, obwohl, lassen Sie Sie sich vertraut mit einigen der Bibliotheken und APIs, die wir zum erstellen dieser Anwendung.

Reagieren die Kontext-API

Reagieren 16.3 wurde eine neue, stabile version des Kontext-API. Wenn Sie gearbeitet haben, viel mit Reagieren, in der Vergangenheit, können Sie vertraut sein mit, wie die Daten weitergegeben von Eltern auf das Kind durch Requisiten. Wenn Sie bestimmte Daten, die erforderlich sind, die durch eine Vielzahl von Komponenten, können Sie am Ende tun, was ist bekannt als prop-Bohrung — übergabe von Daten über mehrere Ebenen von der Komponente Baum, um die Daten an eine Komponente, die es braucht.

Kontext hilft den Schmerz lindern, der prop-Bohrung durch die Bereitstellung einer Möglichkeit zum austauschen von Daten zwischen den Komponenten, ohne dass explizit passieren, dass die Daten durch die Komponente Baum, so dass Sie perfekt für die Speicherung der Authentifizierung.

Wenn wir es schaffen, Kontext, bekommen wir einen Provider-und Consumer-pair-Mädchen. Der Anbieter wird als “smart,” stateful-Komponente, enthält die state machine definition, und unterhält eine Aufzeichnung der aktuellen Zustand unserer Anwendung.

xstate

xstate ist eine JavaScript-Bibliothek für die funktionelle, stateless endlichen Zustandsautomaten und statecharts — es wird uns ein schönes, sauberes API für die Verwaltung von Definitionen und die übergänge durch unseren Staaten.

Eine stateless endlicher Automat Bibliothek klingt vielleicht etwas seltsam, aber im wesentlichen, was es bedeutet, ist, dass xstate kümmert sich nur um den Zustand und die transition, die Sie übergeben haben — was bedeutet, es ist bis zu Ihrer Anwendung zu verfolgen Ihre eigenen aktuellen Zustand.

xstate hat eine Menge von features erwähnenswert, dass wir gewonnen ‘ T decken vieles in diesem Artikel (da wir nur anfangen zu kratzen die Oberfläche auf statecharts): hierarchische Maschinen, parallele Maschinen, Geschichte, Staaten, und die Wachen, um nur einige zu nennen.

Der Ansatz

Also, wir hatten jetzt ein bisschen eine Einführung in die beiden Rahmen und xstate, reden wir über die Vorgehensweise werden wir.

Zunächst werden wir definieren den Rahmen für unsere Anwendung erstellt dann eine stateful <App / > – Komponente (unser provider) enthalten wird unser authentication state machine, zusammen mit Informationen über den aktuellen Benutzer und eine Methode für den Benutzer Abmelden.

Um die Bühne ein bisschen, werfen wir einen kurzen Blick auf ein CodePen-demo von dem, was wir bauen.

Finden Sie den Stift Authentication state machine Beispiel von Jon Bellah (@jonbellah) auf CodePen.

Also, ohne weitere Umschweife, lasst uns Graben in code!

Definition, Kontext

Das erste, was wir tun müssen, ist zu definieren, unsere Anwendungs-Kontext und stellen Sie es mit einigen Standard-Werte. Default-Werte im Rahmen sind hilfreich, dass wir den test-Komponenten in isolation, da die default-Werte werden nur verwendet, wenn keine passende Anbieter.

Für unsere Anwendung, die wir sind gehen, um ein paar Standardwerte: authState die die Authentifizierung Stand der aktuelle Benutzer ein Objekt namens user, die enthalten Daten über unsere Benutzer, wenn Sie authentifiziert sind, dann ein logout () – Methode, die aufgerufen werden können überall in der app, wenn der Benutzer authentifiziert ist.

const Auth = Reagieren.createContext({
authState: ‘login’,
logout: () => {},
Benutzer: {},
});

Die Definition unserer Maschine

Wenn wir darüber nachdenken, wie die Authentifizierung verhält sich in einer Anwendung, in seiner einfachsten form, es gibt drei primäre Zustände: abgemeldet, angemeldet, und laden. Dies sind die drei Staaten, die wir graphisch dargestellt früher.

Rückblick auf das Zustandsdiagramm, unsere Maschine besteht aus den gleichen drei Staaten: abgemeldet, angemeldet, und laden. Wir haben auch vier unterschiedliche Aktionen ausgelöst werden können: SENDEN, ERFOLG, SCHEITERN, und LOGOUT.

Wir können das Modell, dass das Verhalten in code etwa so:

const appMachine = Maschine({
erste: ‘loggedOut’,
Staaten: {
loggedOut: {
onEntry: [‘Fehler’],
auf: {
SUBMIT: ‘laden’,
},
},
laden: {
auf: {
ERFOLG: ‘loggedIn’,
FAIL: ‘loggedOut’,
},
},
loggedIn: {
onEntry: [‘setUser’],
onExit: [‘unsetUser’],
auf: {
LOGOUT: ‘loggedOut’,
},
},
},
});

Wir haben also einfach ausgedrückt, das Diagramm von oben in code, aber sind Sie bereit für mich zu lassen Sie ein kleines Geheimnis? Das Diagramm wurde erzeugt dieser code mit David Khourshid ist xviz — Bibliothek, die verwendet werden, um visuell erkunden der eigentliche code, dass die Befugnisse Ihrer state-machines.

Wenn Sie interessiert sind, Tauchen Sie tiefer in komplexen Benutzeroberflächen mittels finite-state-machines, David Khourshid hat einen entsprechenden Artikel hier auf CSS-Tricks lohnt sich.

Dies ist ein unglaublich mächtiges Werkzeug, wenn Sie versuchen zu Debuggen problematisch Staaten in Ihrer Anwendung.

Zurückkommend auf die obige code nun definieren wir unsere erstmalige Anwendung der Staat — der wir hier aufrufen loggedOut, da wir zeigen wollen, der login-screen beim ersten Besuch.

Beachten Sie, dass in einer typischen Anwendung, würden Sie wahrscheinlich wollen, um aus der be-Staat und zu bestimmen, ob der Benutzer vorher authentifiziert… aber da wir gerade beim faken der login-Prozess, wir sind ab den ausgeloggten Zustand.

In den Staaten-Objekt, definieren wir jedem unserer Mitgliedstaaten sowie die entsprechenden Maßnahmen und übergänge für jedes dieser Staaten. Und dann gehen wir alle, als ein Objekt zu der Maschine () – Funktion, die importiert wird xstate.

Zusammen mit unseren loggedOut und loggedIn Staaten, die wir definiert haben einige Aktionen, die wir wollen, zu feuern, wenn Sie unsere Anwendung eingibt oder wird beendet diesen Staaten. Sehen wir uns an, was diese Aktionen in einem bit.

Dies ist unser Staat Maschine.

Brechen Dinge nach unten, noch mal, wir schauen uns das loggedOut: {: { SUBMIT: ‘laden’} } Linie . Dies bedeutet, dass, wenn unsere Anwendung ist in der loggedOut Zustand und wir rufen unsere übergangs-Funktion mit einer Klage EINREICHEN, wird unsere Anwendung stets den übergang von der loggedOut Zustand der Belastung Stand. Wir können machen, dass der übergang durch aufrufen von appMachine.übergang(‘loggedOut’, ‘SUBMIT’).

Von dort aus, den Ladestatus wird entweder verschieben Sie die Benutzer als authentifizierter Benutzer oder schicken Sie zurück zum login-screen und eine Fehlermeldung angezeigt.

Die Erstellung unserer Kontext-Anbieter

Der Kontext-Anbieter wird die Komponente, die sitzt auf der obersten Ebene der Anwendung und beherbergt alle Daten, die zu einem authentifizierten oder nicht authentifizierten Benutzer.

Sie arbeiten in der gleichen Datei wie unsere state machine definition, erstellen wir eine <App / > – Komponente, und legen Sie es mit allem, was wir brauchen. Keine Sorge, wir werden bedecken, was jeder Methode in nur einem Augenblick.

Klasse App erweitert Reagieren.Komponente {
Konstruktor(props) {
super(props);
diese.state = {
authState: appMachine.initialState.Wert,
Fehler: “,
Abmelden: e => diese.logout(e),
Benutzer: {},
};
}

übergang(Ereignis) {
const nextAuthState = appMachine.übergang(dies.Zustand.authState, event.Typ);
const nextState = nextAuthState.Aktionen.reduzieren(
(Zustand, Aktion) => diese.Befehl(Aktion, Ereignis) || state,
undefined,
);
diese.setState({
authState: nextAuthState.Wert,
…nextState,
});
}

Befehl(Aktion, Ereignis) {
switch (Aktion) {
Fall ‘setUser’:
if (event.username) {
return { user: { name: event.username } };
}
break;
Fall ‘unsetUser’:
return {
Benutzer: {},
};
Fall ‘Fehler’:
if (event.Fehler) {
return {
Fehler: Ereignis.Fehler,
};
}
break;
Standard:
break;
}
}

logout(e) {
e.preventDefault();
diese.übergang({ type: ‘LOGOUT’ });
}

render() {
return (
<Auth.Anbieter value={das.Staat}>
<div className=”w5″>
<div className=”mb2”>{dies.Zustand.error}</div>
{dies.Zustand.authState === ‘loggedIn’ ? (
<Dashboard />
) : (
<Login-transition={event => diese.übergang(Ereignis)} />
)}
</div>
</Auth.Provider ->
);
}
}

Puh, das war eine ganze Menge code! Lassen Sie uns brechen Sie in handliche Stücke, indem Sie einen Blick auf jede Methode dieser Klasse individuell.

Im Konstruktor(), setzen wir unsere Komponente Zustand in den ersten Zustand unserer appMachine, sowie die Einstellung unserer logout-Funktion im Staat, so dass es sein kann, Durchlaufen unsere Anwendungs-Kontext für jeden Verbraucher, die es braucht.

Im übergang () – Methode, wir machen ein paar wichtige Dinge. Erstens wollen wir die übergabe unserer aktuellen Zustand der Anwendung und den Typ des Ereignisses oder der Aktion zu xstate, damit wir feststellen können, unseren nächsten Zustand. Dann, nextState, wir nehmen Sie alle Aktionen im Zusammenhang mit, dass im nächsten Zustand (dies ist eines unserer onEntry oder onExit-Aktionen) und führen Sie Sie durch den Befehl (Methode) — dann nehmen wir alle von den Ergebnissen und stellen unsere neuen Zustand der Anwendung.

In der command () – Methode haben wir eine switch-Anweisung, die ein Objekt zurückgibt — je nach Aktion-Typ, die wir verwenden, um die übergabe der Daten in unserer Anwendung Zustand. Auf diese Weise, sobald ein Benutzer authentifiziert hat, können wir relevante Informationen über diesen Benutzer — Benutzername, E-Mail, id, etc. — in unserem Kontext, so dass es zu einem unserer consumer-Komponenten.

Schließlich, in unserer render () – Methode, sind wir eigentlich die Definition der provider-Komponente und übergeben alle unsere aktuellen Zustand durch den Wert Requisiten, die macht des Staates zur Verfügung, um alle Komponenten unter ihm in der Komponentenstruktur. Dann, je nach Zustand der Anwendung, wir sind rendering-entweder im dashboard oder auf der login-Formular für den Benutzer.

In diesem Fall haben wir eine ziemlich flache Komponentenstruktur unter unseren provider (Auth.Anbieter), aber denken Sie daran, dass der Kontext ermöglicht es, dass der Wert vorhanden sein, um jede Komponente unter unseren Anbieter in der Komponenten-Baum, unabhängig von der Tiefe. So, zum Beispiel, wenn wir eine Komponente, die geschachtelte drei oder vier Stufen hinunter, und wir wollen, um den aktuellen Benutzernamen, wir können nur greifen, dass aus dem Zusammenhang, eher als bohren Sie den ganzen Weg hinunter zu einer Komponente.

Erstellen von Kontext-Verbraucher

Jetzt erstellen wir einige Komponenten, die verbrauchen unsere Anwendungs-Kontext. Aus diesen Komponenten können wir tun alle möglichen Dinge.

Wir können beginnen, durch den Aufbau einer login-Komponente für unsere Anwendung.

class Login extends Component {
Konstruktor(props) {
super(props);
diese.state = {
IhrName: “,
}
diese.handleInput =.handleInput.bind(this);
}

handleInput(e) {
diese.setState({
IhrName: e.Ziel.Wert,
});
}

login(e) {
e.preventDefault();
diese.Requisiten.übergang({ type: ‘SUBMIT’ });
setTimeout(() => {
if (dies.Zustand.yourName) {
wieder dieses.Requisiten.übergang({
Typ: ‘ERFOLG’,
Benutzername: dies.Zustand.IhrName,
}, () => {
diese.setState({ username: “});
});
}
wieder dieses.Requisiten.übergang({
Typ: “FAIL”,
Fehler: “Uh oh, Sie müssen geben Sie Ihren Namen ein!’,
});
}, 2000);
}

render() {
return (
<Auth.Verbraucher>
{({ authState }) => (
<form onSubmit={e => diese.login(e)}>
<label htmlFor=”IhrName”>
<span>Ihr name</span>
<input
id=”yourName”
name=”IhrName”
type=”text”
Wert={das.Zustand.yourName}
onChange={das.handleInput}
/>
</label>
<input
type=”submit”
Wert={authState === ‘be’ ? ‘Logging in…’ : ‘Login’ }
deaktiviert={authState === ‘be’ ? true : false}
/>
</form>
)}
</Auth.Verbraucher>
);
}
}

Oh my! Das war ein großes Teil der code, also lassen Sie uns gehen Sie durch jede Methode wieder.

Im Konstruktor(), wir deklarieren unsere Standard-Zustand und die Bindung der handleInput () – Methode, sodass es verweist auf die ordnungsgemäße diese intern.

In handleInput(), nehmen wir den Wert unserer Formular-Feld aus unserer render () – Methode auf und setzen diesen Wert in der state — dies bezeichnet man als kontrollierte form.

Die login () – Methode ist, wo Sie normalerweise legen Sie Ihre Authentifizierung Logik. Im Falle dieser app, wir sind nur vorgetäuscht, eine Verzögerung mit setTimeout() und entweder die Authentifizierung der Benutzer — wenn Sie haben einen Namen — oder einen Fehler, falls das Feld leer. Beachten Sie, dass der übergang () – Funktion, die es fordert, ist eigentlich die, die wir definiert in <App / > – Komponente, die überliefert wurde über Requisiten.

Schließlich, unserer render () – Methode zeigt unser login-Formular, aber beachten Sie, dass die <Login / > – Komponente ist auch ein Kontext-Verbraucher. Wir sind mit der authState Kontext, um zu bestimmen, ob oder nicht, um zu zeigen, unsere login-Schaltfläche in einer deaktiviert laden Zustand.

Mit Rahmen aus den tiefen der Komponente Baum

Jetzt haben wir behandelt die Entstehung unserer state-Maschine und eine Möglichkeit für Benutzer zum login für unsere Anwendung können wir jetzt darauf angewiesen, dass Informationen über die Benutzer innerhalb jeder Komponente verschachtelt unter unserer <Dashboard / > – Komponente — denn es wird immer nur erbracht werden, wenn der Benutzer angemeldet ist in.

So erstellen wir eine zustandslose Komponente, der packt den Benutzernamen des aktuellen authentifizierten Benutzer und zeigt eine Willkommensmeldung. Da wir vorbei sind die logout () – Methode, um alle unsere Verbraucher, wir können auch geben dem Benutzer die Möglichkeit der Abmeldung von überall in der Komponentenstruktur.

const Dashboard = () => (
<Auth.Verbraucher>
{({ user, logout }) => (
<div>
<div>Hallo {user.name}</div>
<button onClick={e = ” > “Abmelden” (e)}>
Logout
</button>
</div>
)}
</Auth.Verbraucher>
);

Bauen größere Anwendungen mit statecharts

Mit finite-state-machines mit Reagieren nicht nur auf die Authentifizierung, noch ist es beschränkt auf die Kontext-API.

Mit statecharts, können Sie hierarchische-Maschinen und/oder parallel-Maschinen — das bedeutet, die einzelnen Komponenten Reagieren können, haben Ihre eigenen internen Zustand der Maschine, aber noch angeschlossen werden, um den gesamten Zustand der Anwendung.

In diesem Artikel haben wir in Erster Linie auf die Verwendung von xstate direkt mit den nativen Kontext-API; in größeren Anwendungen, ich empfehle Blick auf reagieren-Automaten, die eine dünne Abstraktionsschicht über der Oberseite von xstate. reagieren-Automaten hat den zusätzlichen Vorteil des seins in der Lage, automatisch zu generieren Scherz-tests für die Komponenten.

Zustand Maschinen und state-management-tools sind nicht sich gegenseitig ausschließende

Es ist einfach zu verwirrt durch das denken, müssen Sie entweder verwenden, sagen, xstate oder Redux; aber es ist wichtig zu beachten, dass state-machines sind eher eine Umsetzung Konzept, besorgt mit, wie Sie entwerfen Ihr Zustand — nicht unbedingt, wie Sie es zu verwalten.

In der Tat, Zustand-Maschinen verwendet werden kann mit gerade ungefähr jedem un-rechthaberisch-state-management-tool. Ich möchte Sie ermutigen, zu erforschen verschiedene Ansätze, um festzustellen, was funktioniert am besten für Sie, Ihr team und Ihre Anwendung(s).

Abschließend

Diese Konzepte erweitert werden, um der realen Welt die zimmerreserviereung, ohne das umgestalten Ihrer gesamten Anwendung. State machines sind eine ausgezeichnete umgestalten-Ziel — Sinn, das nächste mal, arbeitest du gerade an einer Komponente, die übersät ist mit boolean-flags wie isFetching und ISTFEHLER, betrachten die Umgestaltung, die Komponente für die Verwendung einer state-Maschine.

Als front-end-Entwickler, ich habe festgestellt, dass ich oft Befestigung von zwei Kategorien von Fehlern: display-Problemen oder unerwarteten Anwendung Staaten.

Zustandsautomaten stellen die zweite Kategorie praktisch verschwinden.

Wenn Sie interessiert sind, Tauchen Sie tiefer in state machines, ich habe in den letzten paar Monaten an einem Kurs finite-state-Maschinen — wenn Sie sich für die E-Mail-Liste, erhalten Sie einen Rabatt-code, wenn der Kurs startet im August.