Articles

Compararea mașinilor de stare: XState vs. Robot

Gestionarea stării în React poate deveni greoaie pe măsură ce logica aplicației devine din ce în ce mai complexă. Bibliotecile terțe, cum ar fi Redux, Flux și MobX, ajută, dar chiar și aceste instrumente vin cu propriile costuri suplimentare.

O mașină de stare, numită și mașină cu stări finite sau automate cu stări finite, este un model matematic de calcul. Este o mașină abstractă cu un număr finit de stări la un moment dat.

În acest ghid, vom trece în revistă asemănările, diferențele, avantajele și dezavantajele a două mașini de stare – XState și Robot – și vom explica cum să le folosim pentru a simplifica gestionarea stării în aplicațiile React.

De ce să folosim o mașină de stare?

Starea este o parte importantă a majorității aplicațiilor frontend, în special în React. Gândiți-vă la stare ca la o reprezentare a părții unei aplicații care se schimbă.

Considerați o componentă care preia date de la o 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> ); }

În acest exemplu, datele sunt starea noastră, deoarece este partea aplicației care se schimbă ori de câte ori apare un eveniment – în acest caz, click-ul pe un buton. Problema cu această configurație este că poate deveni complicată.

Ce se întâmplă în timp ce utilizatorul așteaptă ca înregistrarea să fie preluată sau dacă apare o eroare în timpul preluării? Trebuie să adăugăm mai multe stări pentru a gestiona aceste probleme.

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

Dacă aplicația dvs. este complexă, lucrurile pot scăpa rapid de sub control pe măsură ce se adaugă noi caracteristici, făcând codul dvs. dificil de înțeles, testat și îmbunătățit.

Mașinile de stări abordează diferit această problemă unică. Cu mașinile de stare, puteți defini toate stările în care se poate afla aplicația noastră, tranzițiile între stări și efectele secundare care pot apărea. Acest lucru vă ajută să evitați o situație în care aplicația se află într-o stare imposibilă.

State Diagram

State Diagram

Aplicația noastră se poate afla în următoarele stări:

  1. ready – starea inițială când aplicația pornește
  2. loading – când apare un eveniment i.e un buton este apăsat
  3. success – când încărcarea se rezolvă
  4. error – când încărcarea este respinsă

Aplicația trece de la o stare la alta atunci când se declanșează o acțiune – i.e, atunci când un utilizator face clic pe un buton. Aveți un control mai bun asupra aplicației dvs. atunci când puteți anticipa toate stările posibile în care se poate afla.

Ce fac XState și Robot?

Potrivit documentației sale oficiale, XState este o bibliotecă pentru crearea, interpretarea și executarea mașinilor cu stări finite și a diagramelor de stare, precum și pentru gestionarea invocărilor acestor mașini ca actori. A fost creată de David Khourshid pentru a aborda problemele de stare în interfețele de utilizator.

Robot este o bibliotecă ușoară, funcțională și imuabilă creată de Mathew Philips pentru construirea mașinilor cu stări finite. A fost inspirată de XState, Statecharts și de limbajul de programare P.

Precondiții

Pentru a urmări acest tutorial, veți avea nevoie de:

  • Cunoștințe de JavaScript
  • Cunoștințe de React
  • yarn sau npm v5.2 sau mai mare
  • Node versiunea 10 sau mai mare

Începem

Pentru a demonstra asemănările și diferențele dintre XState și Robot, vom crea o aplicație care preia date de la o API.

Deschideți un terminal și inițializați o aplicație React.

npx create-react-app state-machine

Aceasta creează o aplicație React numită State Machine.

În continuare, creați un serviciu pentru a prelua datele de la API.

cd src && touch fetchTodo.js

Comanda de mai sus creează un fișier numit fetchTodo.js în directorul src.

Deschideți fișierul și introduceți următoarele.

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

În principiu, de fiecare dată când este apelată funcția fetchTodo, aceasta returnează datele recuperate din API.

Instalare

XState poate fi instalat folosind npm sau yarn sau încorporând scriptul prin intermediul unui CDN.

Pentru a instala biblioteca folosind npm, deschideți un terminal și rulați:

npm install xstate @xstate/react

Aceasta instalează biblioteca de bază xstate și un pachet pentru React numit @xstate/react care vă permite să folosiți cârlige XState personalizate în aplicațiile React.

Puteți instala Robot folosind npm sau yarn, dar nu și un CDN.

Pentru a instala Robot, lansați un terminal și rulați următoarea comandă.

npm install robot3 react-robot

Robot oferă, de asemenea, un pachet pentru a utiliza cârlige personalizate în React numit react-robot

Crearea unei mașini

Înainte de a putea utiliza o mașină de stări, trebuie mai întâi să o definiți.

În directorul src, creați un fișier numit xstateMachine.js. Copiați codul de mai jos în fișierul creat.

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

Mașinile sunt definite folosind funcția factory Machine(). Mașina pe care am definit-o în codul de mai sus este alcătuită din ID-uri, stări, contexte, acțiuni și tranziții. ID-urile sunt folosite pentru a identifica nodurile de stare.

Statele din mașină sunt:

  • ready
  • loading
  • success
  • error

Contextul este o stare extinsă care este folosită pentru a reprezenta date cantitative, cum ar fi numere, șiruri arbitrare, obiecte etc. Starea inițială a aplicației este definită ca ready. Atunci când se face clic pe un buton, are loc o tranziție, care mută starea de la ready la loading.

În starea loading, există o proprietate invoke care este responsabilă fie pentru rezolvarea, fie pentru respingerea unei promisiuni. Ori de câte ori promisiunea fetchTodo este rezolvată, starea loading trece în starea success, iar acțiunea assign actualizează contextul cu rezultatul obținut din promisiune. Dacă este respinsă, se trece la starea error.

Crearea unei mașini cu Robot este similară, deși cu câteva diferențe esențiale. O diferență majoră este că, deoarece Robot este o bibliotecă funcțională, majoritatea acțiunilor sunt efectuate folosind funcții, spre deosebire de XState, care folosește obiecte de opțiune.

Crearea unui fișier numit robotMachine.js în directorul src și lipiți următoarele.

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

În Robot, mașinile sunt create folosind funcția createMachine, care acceptă un obiect. O stare este definită cu ajutorul funcției state și poate accepta ca parametru un transition.

Mutarea de la o stare la alta se face cu ajutorul funcției transition, care acceptă ca parametri evenimentul și următoarea stare. Opțional, funcția reduce poate fi adăugată la transition ca un al treilea parametru. Funcțiile de reducere acceptă ca parametru o funcție de reducere, care este utilizată pentru a actualiza contextul.

Robot are, de asemenea, o funcție invoke, similară cu proprietatea invoke din XState. Atunci când aplicația se află în starea loading, funcția invoke este apelată. Funcția invoke este un tip de stare care invocă o promisiune și returnează o funcție sau o altă mașină. În cazul în care funcția invoke rezolvă promisiunea, aceasta va trimite un eveniment done. Dacă este respinsă, trimite un eveniment error.

Constituirea componentei

Acum că mașina noastră este gata, următorul pas este să construim o componentă care va utiliza mașina.

Creați un fișier în directorul src pentru componentă și lipiți următoarele.

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;

Pentru a utiliza o mașină trebuie să importăm cârligul useMachine din @xstate/react library, precum și mașina pe care am creat-o mai devreme.

Cârligul useMachine este un React Hook care interpretează o mașină. Este responsabil pentru pornirea unui serviciu care să ruleze pe tot parcursul ciclului de viață al unei componente.

Cârligul useMachine acceptă o mașină ca parametru și returnează un array. Matricea conține starea current și send, care este o funcție care trimite un eveniment către serviciul creat de cârligul useMachine.

Starea current este un obiect care conține starea, contextul și unele funcții utilitare. Pentru a verifica starea curentă, utilizați proprietatea matches, care returnează un boolean. Atunci când un utilizator face clic pe buton, acesta trimite un eveniment către serviciu. Acesta verifică apoi starea curentă a mașinii și redă interfața de utilizator corespunzătoare pe baza stării.

Abordarea Robot pentru construirea componentelor este similară. O componentă construită cu Robot ar arăta astfel:

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 are, de asemenea, un cârlig useMachine care poate fi accesat prin importarea react-robot library. Diferența în implementare constă în modul în care este comparată o stare. În timp ce XState folosește proprietatea matches, care este o funcție care acceptă șirul pe care încercăm să îl comparăm, Robot folosește proprietatea name pentru a verifica starea curentă înainte de a compara.

Concluzie

Mașinile de stare oferă o modalitate mult mai bine organizată de a gestiona starea în aplicațiile React și sunt ușor de scalat în comparație cu alte alternative. XState și Robot sunt două biblioteci foarte populare care, deși foarte asemănătoare, abordează manipularea mașinilor din puncte de vedere foarte diferite.

Rezervația pentru acest tutorial este disponibilă pe GitHub.

Pentru mai multe informații, consultați următoarele resurse.

  • Documentația Robot
  • Documentația XState
  • „Infinitely Better UIs with Finite Automata” (video)

Vizibilitate completă în aplicațiile React de producție

Depanarea aplicațiilor React poate fi dificilă, în special atunci când utilizatorii întâmpină probleme care sunt dificil de reprodus. Dacă sunteți interesat să monitorizați și să urmăriți starea Redux, să scoateți la suprafață în mod automat erorile JavaScript și să urmăriți solicitările lente de rețea și timpul de încărcare a componentelor, încercați LogRocket. LogRocket Dashboard Free Trial Banner

LogRocket este ca un DVR pentru aplicațiile web, înregistrând literalmente tot ceea ce se întâmplă în aplicația dumneavoastră React. În loc să ghiciți de ce apar problemele, puteți agrega și raporta în ce stare se afla aplicația dvs. atunci când a apărut o problemă. LogRocket monitorizează, de asemenea, performanța aplicației dumneavoastră, raportând cu măsurători cum ar fi sarcina CPU a clientului, utilizarea memoriei clientului și multe altele.

Pachetul de middleware LogRocket Redux adaugă un nivel suplimentar de vizibilitate în sesiunile de utilizator. LogRocket înregistrează toate acțiunile și starea din magazinele dvs. Redux.

Modernizați modul în care vă depanați aplicațiile React – începeți să monitorizați gratuit.

.