Articles

Porovnání stavových strojů: XState vs. Robot

S rostoucí složitostí aplikační logiky se může stát správa stavu v Reactu těžkopádnou. Pomáhají knihovny třetích stran, jako jsou Redux, Flux a MobX, ale i tyto nástroje mají svou režii.

Stavový stroj, nazývaný také konečný stavový stroj nebo konečný stavový automat, je matematický model výpočtu. Je to abstraktní stroj s konečným počtem stavů v daném okamžiku.

V této příručce si projdeme podobnosti, rozdíly, výhody a nevýhody dvou stavových automatů – XState a Robot – a projdeme si, jak je použít pro zjednodušení správy stavu v aplikacích React.

Proč používat stavový automat?

Stav je důležitou součástí většiny frontendových aplikací, zejména v Reactu. Představte si stav jako reprezentaci části aplikace, která se mění.

Představte si komponentu, která načítá data z rozhraní 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> ); }

V tomto příkladu jsou data naším stavem, protože je to část aplikace, která se změní, kdykoli nastane událost – v tomto případě kliknutí na tlačítko. Problémem tohoto nastavení je, že se může zkomplikovat.

Co se stane, když uživatel čeká na načtení záznamu nebo když při načítání dojde k chybě? Musíme přidat další stavy, abychom tyto problémy zvládli.

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 vaše aplikace složitá, mohou se věci s přidáváním nových funkcí rychle vymknout kontrole, což ztěžuje pochopení, testování a vylepšování kódu.

Stavy přistupují k tomuto jedinečnému problému jinak. Pomocí stavových strojů můžete definovat všechny stavy, ve kterých se naše aplikace může nacházet, přechody mezi stavy a vedlejší efekty, které mohou nastat. To vám pomůže vyhnout se situaci, kdy je aplikace v nemožném stavu.

State Diagram

State Diagram

Naše aplikace může být v následujících stavech:

  1. ready – počáteční stav při spuštění aplikace
  2. loading – když nastane událost, tj.např. kliknutí na tlačítko
  3. success – když se načítání vyřeší
  4. error – když je načítání zamítnuto

Aplikace přechází z jednoho stavu do druhého, když je spuštěna akce – tj, když uživatel klikne na tlačítko. Aplikaci máte lépe pod kontrolou, když můžete předvídat všechny možné stavy, ve kterých se může nacházet.

Co dělají XState a Robot?

Podle oficiální dokumentace je XState knihovna pro vytváření, interpretaci a spouštění konečných stavových strojů a stavových diagramů a také pro správu volání těchto strojů jako aktérů. Vytvořil ji David Khourshid k řešení problémů se stavy v uživatelských rozhraních.

Robot je lehká, funkční a neměnná knihovna vytvořená Mathewem Philipsem pro vytváření konečných stavových strojů. Inspirovaly ji XState, Statecharts a programovací jazyk P.

Předpoklady

K tomu, abyste mohli postupovat podle tohoto kurzu, budete potřebovat:

  • Znalost jazyka JavaScript
  • Znalost jazyka React
  • yarn nebo npm v5.2 nebo vyšší
  • Node verze 10 nebo vyšší

Začínáme

Pro demonstraci podobností a rozdílů mezi XState a Robotem vytvoříme aplikaci, která načítá data z rozhraní API.

Otevřete terminál a inicializujte aplikaci React.

npx create-react-app state-machine

Tím vytvoříte aplikaci React s názvem State Machine.

Následujte vytvoření služby pro načítání dat z API.

cd src && touch fetchTodo.js

Výše uvedený příkaz vytvoří soubor s názvem fetchTodo.js v adresáři src.

Otevřete soubor a zadejte následující příkaz.

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

V podstatě kdykoli bude zavolána funkce fetchTodo, vrátí data získaná z API.

Instalace

XState lze nainstalovat pomocí npm nebo yarn nebo vložením skriptu prostřednictvím CDN.

Chcete-li knihovnu nainstalovat pomocí npm, otevřete terminál a spusťte:

npm install xstate @xstate/react

Tím se nainstaluje základní knihovna xstate a balíček pro React s názvem @xstate/react, který umožňuje používat vlastní háčky XState v aplikacích React.

Robota můžete nainstalovat pomocí npm nebo yarn, ale ne pomocí CDN.

Pro instalaci Robota spusťte terminál a spusťte následující příkaz.

npm install robot3 react-robot

Robot také nabízí balíček pro použití vlastních háčků v Reactu s názvem react-robot

Vytvoření stroje

Před použitím stavového stroje jej musíte nejprve definovat.

V adresáři src vytvořte soubor s názvem xstateMachine.js. Do vytvořeného souboru zkopírujte níže uvedený kód.

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

Stavy strojů se definují pomocí funkce Machine() factory. Stroj, který jsme definovali v kódu výše, se skládá z ID, stavů, kontextů, akcí a přechodů. ID slouží k identifikaci stavových uzlů.

Stavy ve stroji jsou:

  • ready
  • loading
  • success
  • error

Kontext je rozšířený stav, který se používá k reprezentaci kvantitativních dat, jako jsou čísla, libovolný řetězec, objekty atd. Počáteční stav aplikace je definován jako ready. Po kliknutí na tlačítko dojde k přechodu, který přesune stav z ready do loading.

Ve stavu loading je vlastnost invoke, která je zodpovědná za vyřešení nebo odmítnutí slibu. Kdykoli je slib fetchTodo vyřešen, stav loading přejde do stavu success a akce assign aktualizuje kontext výsledkem získaným ze slibu. Pokud je odmítnut, přechází do stavu error.

Vytvoření stroje s robotem je podobné, i když s některými zásadními rozdíly. Jedním z hlavních rozdílů je, že protože Robot je funkční knihovna, většina akcí se provádí pomocí funkcí, na rozdíl od XState, který používá volitelné objekty.

Vytvořte soubor robotMachine.js v adresáři src a vložte do něj následující.

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

V Robotu se stroje vytvářejí pomocí funkce createMachine, která přijímá objekt. Stav se definuje pomocí funkce state a jako parametr může přijímat transition.

Přechod z jednoho stavu do druhého se provádí pomocí funkce transition, která jako parametry přijímá událost a další stav. Volitelně lze k funkci transition přidat jako třetí parametr funkci reduce. Redukční funkce přijímají jako parametr funkci reducer, která se používá k aktualizaci kontextu.

Robot má také funkci invoke, podobnou vlastnosti invoke v XState. Když je aplikace ve stavu loading, volá se funkce invoke. Funkce invoke je druh stavu, který vyvolává slib a vrací funkci nebo jiný stroj. Pokud funkce invoke vyřeší slib, odešle událost done. Pokud je odmítnuta, odešle událost error.

Sestavení komponenty

Teď, když je náš stroj připraven, je dalším krokem sestavení komponenty, která bude stroj využívat.

Vytvořte v adresáři src soubor pro komponentu a vložte následující.

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;

Pro použití stroje musíme importovat háček useMachine z @xstate/react library a také stroj, který jsme vytvořili dříve.

Háček useMachine je háček React, který interpretuje stroj. Je zodpovědný za spuštění služby, která má běžet po celou dobu životního cyklu komponenty.

Háček useMachine přijímá stroj jako parametr a vrací pole. Pole obsahuje stav current a send, což je funkce, která posílá událost službě vytvořené háčkem useMachine.

Stav current je objekt, který obsahuje stav, kontext a některé užitečné funkce. Chcete-li zjistit aktuální stav, použijte vlastnost matches, která vrací hodnotu boolean. Když uživatel klikne na tlačítko, odešle do služby událost. Ta pak zkontroluje aktuální stav stroje a na základě tohoto stavu vykreslí příslušné uživatelské rozhraní.

Přístup robota k sestavování komponent je podobný. Komponenta sestavená pomocí Robota by vypadala takto:

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 má také háček useMachine, ke kterému lze přistupovat importem react-robot library. Rozdíl v implementaci je ve způsobu porovnávání stavu. Zatímco XState používá vlastnost matches, což je funkce, která přijímá řetězec, který se snažíme porovnat, Robot používá vlastnost name pro kontrolu aktuálního stavu před porovnáním.

Závěr

State machines nabízí mnohem lépe organizovaný způsob správy stavu v aplikacích React a ve srovnání s jinými alternativami se snadno škálují. XState a Robot jsou dvě velmi populární knihovny, které jsou si sice velmi podobné, ale ke zpracování strojů přistupují z velmi odlišných hledisek.

Repozitář pro tento tutoriál je k dispozici na GitHubu.

Pro více informací se podívejte na následující zdroje.

  • Dokumentace k robotu
  • Dokumentace k XState
  • „Infinitely Better UIs with Finite Automata“ (video)

Plný přehled o produkčních aplikacích React

Ladění aplikací React může být obtížné, zejména pokud se u uživatelů objeví problémy, které je obtížné reprodukovat. Pokud máte zájem o monitorování a sledování stavu Reduxu, automatické odhalování chyb JavaScriptu a sledování pomalých síťových požadavků a doby načítání komponent, vyzkoušejte LogRocket. LogRocket Dashboard Free Trial Banner

LogRocket je jako DVR pro webové aplikace, který zaznamenává doslova vše, co se ve vaší aplikaci React děje. Místo hádání, proč k problémům dochází, můžete agregovat a reportovat, v jakém stavu se vaše aplikace nacházela, když k problému došlo. LogRocket také monitoruje výkon vaší aplikace a podává zprávy s metrikami, jako je zatížení procesoru klienta, využití paměti klienta a další.

Mediální balík LogRocket Redux přidává další vrstvu přehledu o uživatelských relacích. LogRocket zaznamenává všechny akce a stavy z vašich úložišť Redux.

Modernizujte způsob ladění svých aplikací React – začněte monitorovat zdarma.

.