Articles

Porównanie maszyn stanów: XState vs. Robot

Zarządzanie stanem w React może stać się uciążliwe, gdy logika aplikacji staje się coraz bardziej złożona. Pomagają w tym biblioteki innych firm, takie jak Redux, Flux i MobX, ale nawet te narzędzia mają swój własny narzut.

Maszyna stanów, zwana również maszyną stanów skończonych lub automatami stanów skończonych, jest matematycznym modelem obliczeń. Jest to abstrakcyjna maszyna o skończonej liczbie stanów w danym czasie.

W tym przewodniku omówimy podobieństwa, różnice, zalety i wady dwóch maszyn stanów – XState i Robot – oraz przejdziemy przez to, jak ich używać, aby uprościć zarządzanie stanem w aplikacjach React.

Dlaczego warto używać maszyny stanów?

Stan jest ważną częścią większości aplikacji frontendowych, zwłaszcza w React. Pomyśl o stanie jako o reprezentacji części aplikacji, która się zmienia.

Rozważmy komponent, który pobiera dane z 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> ); }

W tym przykładzie dane są naszym stanem, ponieważ jest to część aplikacji, która zmienia się za każdym razem, gdy występuje zdarzenie – w tym przypadku kliknięcie przycisku. Problem z taką konfiguracją polega na tym, że może ona stać się skomplikowana.

Co się stanie, gdy użytkownik będzie czekał na pobranie rekordu lub gdy podczas pobierania wystąpi błąd? Musimy dodać więcej stanów, aby poradzić sobie z tymi problemami.

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

Jeśli twoja aplikacja jest złożona, sprawy mogą szybko wymknąć się spod kontroli, gdy dodawane są nowe funkcje, co sprawia, że twój kod jest trudny do zrozumienia, testowania i ulepszania.

Maszyny stanów podchodzą do tego unikalnego problemu w inny sposób. Dzięki maszynom stanów, można zdefiniować wszystkie stany, w jakich może znajdować się nasza aplikacja, przejścia pomiędzy stanami oraz efekty uboczne, jakie mogą wystąpić. Pozwala to uniknąć sytuacji, w której aplikacja znajduje się w stanie niemożliwym do osiągnięcia.

State Diagram

State Diagram

Nasza aplikacja może znajdować się w następujących stanach:

  1. ready – stan początkowy, gdy aplikacja się uruchamia
  2. loading – gdy wystąpi zdarzenie i.e przycisk jest kliknięty
  3. success – gdy ładowanie rozwiązuje się
  4. error – gdy ładowanie jest odrzucone

Aplikacja przechodzi z jednego stanu do drugiego, gdy zostanie wywołana akcja – tj, gdy użytkownik kliknie przycisk. Masz lepszą kontrolę nad swoją aplikacją, gdy możesz przewidzieć wszystkie możliwe stany, w jakich może się ona znaleźć.

Co robią XState i Robot?

Według oficjalnej dokumentacji, XState jest biblioteką do tworzenia, interpretowania i wykonywania maszyn stanów skończonych i diagramów stanów, a także zarządzania wywołaniami tych maszyn jako aktorami. Została stworzona przez Davida Khourshida w celu rozwiązania problemów związanych ze stanami w interfejsach użytkownika.

Robot jest lekką, funkcjonalną i niezmienną biblioteką stworzoną przez Mathew Philipsa do budowania maszyn stanów skończonych. Została zainspirowana przez XState, Statecharts i język programowania P.

Wymagania wstępne

Aby podążać za tym tutorialem, będziesz potrzebował:

  • znajomości JavaScript
  • znajomości React
  • yarn lub npm v5.2 lub nowsza
  • Węzeł w wersji 10 lub nowszej

Rozpoczynanie

Aby zademonstrować podobieństwa i różnice między XState i Robot, stworzymy aplikację, która pobiera dane z API.

Otwórz terminal i zainicjalizuj aplikację React.

npx create-react-app state-machine

Tworzy to aplikację React o nazwie State Machine.

Następnie utwórz usługę pobierającą dane z API.

cd src && touch fetchTodo.js

Powyższe polecenie tworzy plik o nazwie fetchTodo.js w katalogu src.

Otwórz plik i wprowadź następujące dane.

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

Podstawowo, za każdym razem, gdy wywoływana jest funkcja fetchTodo, zwraca ona dane pobrane z API.

Instalacja

XState można zainstalować za pomocą npm lub yarn lub osadzając skrypt poprzez CDN.

Aby zainstalować bibliotekę za pomocą npm, otwórz terminal i uruchom:

npm install xstate @xstate/react

Instaluje to xstatebibliotekę rdzeniową oraz pakiet dla React o nazwie @xstate/react, który pozwala używać niestandardowych haków XState w aplikacjach React.

Możesz zainstalować Robota przy użyciu npm lub yarn, ale nie CDN.

Aby zainstalować Robota, uruchom terminal i wykonaj następujące polecenie.

npm install robot3 react-robot

Robot oferuje również pakiet do używania niestandardowych haków w React o nazwie react-robot

Tworzenie maszyny

Zanim będziesz mógł użyć maszyny stanów, musisz ją najpierw zdefiniować.

W katalogu src utwórz plik o nazwie xstateMachine.js. Skopiuj poniższy kod do utworzonego pliku.

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

Maszyny definiuje się za pomocą funkcji Machine() factory. Maszyna, którą zdefiniowaliśmy w powyższym kodzie, składa się z identyfikatorów, stanów, kontekstów, akcji i przejść. Identyfikatory służą do identyfikacji węzłów stanu.

Stany w maszynie to:

  • ready
  • loading
  • success
  • error

Kontekst jest stanem rozszerzonym, który służy do reprezentowania danych ilościowych, takich jak liczby, dowolny ciąg znaków, obiekty itp. Stan początkowy aplikacji jest zdefiniowany jako ready. Po kliknięciu przycisku następuje przejście, przenoszące stan z ready do loading.

W stanie loading znajduje się właściwość invoke, która jest odpowiedzialna za rozwiązanie lub odrzucenie obietnicy. Za każdym razem, gdy fetchTodo obietnica zostanie rozwiązana, stan loading przechodzi do stanu success, a akcja assign aktualizuje kontekst o wynik uzyskany z obietnicy. Jeśli zostanie odrzucona, przechodzi do stanu error.

Tworzenie maszyny za pomocą Robota jest podobne, aczkolwiek z kilkoma kluczowymi różnicami. Jedną z głównych różnic jest to, że ponieważ Robot jest biblioteką funkcjonalną, większość działań jest wykonywana przy użyciu funkcji, w przeciwieństwie do XState, który używa obiektów opcji.

Utwórz plik o nazwie robotMachine.js w swoim katalogu src i wklej poniższe elementy.

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

W Robot, maszyny są tworzone przy użyciu funkcji createMachine, która przyjmuje obiekt. Stan jest definiowany za pomocą funkcji state i może przyjmować transition jako parametr.

Przejście z jednego stanu do drugiego odbywa się za pomocą funkcji transition, która przyjmuje zdarzenie i następny stan jako parametry. Opcjonalnie, do funkcji transition można dodać funkcję reduce jako trzeci parametr. Funkcje redukujące przyjmują jako parametr funkcję redukującą, która jest używana do aktualizacji kontekstu.

Robot posiada również funkcję invoke, podobną do właściwości invoke w XState. Gdy aplikacja znajduje się w stanie loading, wywoływana jest funkcja invoke. Funkcja invoke jest rodzajem stanu, który wywołuje obietnicę i zwraca funkcję lub inną maszynę. Jeśli funkcja invoke rozwiąże obietnicę, wyśle zdarzenie done. Jeśli zostanie odrzucona, wyśle zdarzenie error.

Budowanie komponentu

Teraz, gdy nasza maszyna jest gotowa, następnym krokiem jest zbudowanie komponentu, który będzie wykorzystywał maszynę.

Utwórz plik w swoim katalogu src dla komponentu i wklej następujące elementy.

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;

Aby wykorzystać maszynę, musimy zaimportować hak useMachine z @xstate/react library, a także maszynę, którą stworzyliśmy wcześniej.

Hak useMachine jest hakiem Reacta, który interpretuje maszynę. Jest on odpowiedzialny za uruchomienie usługi, która będzie działać przez cały cykl życia komponentu.

Hak useMachine przyjmuje maszynę jako parametr i zwraca tablicę. Tablica zawiera stan current oraz send, który jest funkcją wysyłającą zdarzenie do usługi utworzonej przez hak useMachine.

Stan current jest obiektem, który zawiera stan, kontekst i niektóre funkcje użytkowe. Aby sprawdzić aktualny stan, należy użyć właściwości matches, która zwraca wartość typu boolean. Gdy użytkownik kliknie przycisk, wysyła zdarzenie do usługi. Następnie sprawdza ona bieżący stan maszyny i renderuje odpowiedni UI w oparciu o ten stan.

Podejście Robota do budowania komponentów jest podobne. Komponent zbudowany za pomocą Robota wyglądałby tak:

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 posiada również hak useMachine, do którego można uzyskać dostęp poprzez zaimportowanie react-robot library. Różnica w implementacji polega na sposobie porównywania stanów. Podczas gdy XState używa właściwości matches, która jest funkcją akceptującą ciąg znaków, który próbujemy porównać, Robot używa właściwości name do sprawdzenia bieżącego stanu przed porównaniem.

Podsumowanie

Maszyny stanów oferują znacznie lepiej zorganizowany sposób zarządzania stanem w aplikacjach React i są łatwe do skalowania w porównaniu z innymi alternatywami. XState i Robot to dwie bardzo popularne biblioteki, które, choć bardzo podobne, podchodzą do obsługi maszyn z bardzo różnych punktów widzenia.

Repozytorium dla tego samouczka jest dostępne na GitHub.

Aby uzyskać więcej informacji, sprawdź następujące zasoby.

  • Dokumentacja Robota
  • Dokumentacja XState
  • „Infinitely Better UIs with Finite Automata” (wideo)

Pełna widoczność w produkcyjnych aplikacjach React

Debugowanie aplikacji React może być trudne, zwłaszcza gdy użytkownicy doświadczają problemów, które są trudne do odtworzenia. Jeśli interesuje Cię monitorowanie i śledzenie stanu Redux, automatyczne wyławianie błędów JavaScript oraz śledzenie powolnych żądań sieciowych i czasu ładowania komponentów, wypróbuj LogRocket. LogRocket Dashboard Free Trial Banner

LogRocket jest jak rejestrator dla aplikacji internetowych, nagrywający dosłownie wszystko, co dzieje się w Twojej aplikacji React. Zamiast zgadywać, dlaczego zdarzają się problemy, możesz agregować i raportować, w jakim stanie była Twoja aplikacja, kiedy wystąpił problem. LogRocket monitoruje również wydajność Twojej aplikacji, raportując za pomocą metryk takich jak obciążenie CPU klienta, wykorzystanie pamięci klienta i więcej.

Pakiet oprogramowania pośredniczącego LogRocket Redux dodaje dodatkową warstwę wglądu w sesje użytkowników. LogRocket rejestruje wszystkie działania i stan z Twoich sklepów Redux.

Unowocześnij sposób debugowania swoich aplikacji React – zacznij monitorować za darmo.

.