Otključajte snagu strojeva stanja u Reactu s prilagođenim kukičama. Naučite apstrahirati kompleksnu logiku, poboljšati održavanje koda i izgraditi robusne aplikacije.
React Custom Hook State Machine: Savladavanje apstrakcije kompleksne logike stanja
Kako React aplikacije postaju sve složenije, upravljanje stanjem može postati značajan izazov. Tradicionalni pristupi korištenjem `useState` i `useEffect` mogu brzo dovesti do zapletene logike i koda kojeg je teško održavati, osobito kada se radi o zamršenim prijelazima stanja i nuspojavama. Ovdje u pomoć priskaču strojevi stanja, a posebno React prilagođene kuke koje ih implementiraju. Ovaj će vas članak provesti kroz koncept strojeva stanja, pokazati kako ih implementirati kao prilagođene kuke u Reactu i ilustrirati prednosti koje nude za izgradnju skalabilnih i održivih aplikacija za globalnu publiku.
Što je stroj stanja?
Stroj stanja (ili konačni automat stanja, FSM) je matematički model izračunavanja koji opisuje ponašanje sustava definiranjem konačnog broja stanja i prijelaza između tih stanja. Zamislite to kao dijagram toka, ali s strožim pravilima i formalnijom definicijom. Ključni koncepti uključuju:
- Stanja: Predstavljaju različita stanja ili faze sustava.
- Prijelazi: Definiraju kako se sustav kreće iz jednog stanja u drugo na temelju određenih događaja ili uvjeta.
- Događaji: Okidači koji uzrokuju prijelaze stanja.
- Početno stanje: Stanje u kojem sustav počinje.
Strojevi stanja se ističu u modeliranju sustava s dobro definiranim stanjima i jasnim prijelazima. Primjera ne manjka u stvarnim scenarijima:
- Prometno svjetlo: Ciklira kroz stanja kao što su Crveno, Žuto, Zeleno, s prijelazima pokrenutim tajmerima. Ovo je globalno prepoznatljiv primjer.
- Obrada narudžbe: Narudžba e-trgovine može prelaziti kroz stanja kao što su "Na čekanju", "U obradi", "Poslano" i "Dostavljeno". Ovo se univerzalno odnosi na online maloprodaju.
- Postupak provjere autentičnosti: Postupak provjere autentičnosti korisnika mogao bi uključivati stanja kao što su "Odjavljen", "Prijava", "Prijavljen" i "Greška". Sigurnosni protokoli općenito su dosljedni u svim zemljama.
Zašto koristiti strojeve stanja u Reactu?
Integracija strojeva stanja u vaše React komponente nudi nekoliko uvjerljivih prednosti:
- Poboljšana organizacija koda: Strojevi stanja nameću strukturirani pristup upravljanju stanjem, čineći vaš kod predvidljivijim i lakšim za razumijevanje. Nema više špageti koda!
- Smanjena složenost: Eksplicitnim definiranjem stanja i prijelaza, možete pojednostaviti kompleksnu logiku i izbjeći neželjene nuspojave.
- Poboljšana testabilnost: Strojevi stanja su inherentno testabilni. Možete jednostavno provjeriti ponaša li se vaš sustav ispravno testiranjem svakog stanja i prijelaza.
- Povećana održivost: Deklarativna priroda strojeva stanja olakšava izmjenu i proširenje vašeg koda kako se vaša aplikacija razvija.
- Bolje vizualizacije: Postoje alati koji mogu vizualizirati strojeve stanja, pružajući jasan pregled ponašanja vašeg sustava, pomažući u suradnji i razumijevanju među timovima s različitim skupovima vještina.
Implementacija stroja stanja kao React prilagođene kuke
Ilustrirajmo kako implementirati stroj stanja pomoću React prilagođene kuke. Izradit ćemo jednostavan primjer gumba koji može biti u tri stanja: `idle`, `loading` i `success`. Gumb počinje u stanju `idle`. Kada se klikne, prelazi u stanje `loading`, simulira postupak učitavanja (koristeći `setTimeout`), a zatim prelazi u stanje `success`.
1. Definirajte stroj stanja
Prvo, definiramo stanja i prijelaze našeg stroja stanja gumba:
const buttonStateMachineDefinition = {
initial: 'idle',
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success', // Nakon 2 sekunde, prijeđi u uspjeh
},
},
success: {},
},
};
Ova konfiguracija koristi pristup neovisan o biblioteci (iako inspiriran XStateom) za definiranje stroja stanja. Logiku za interpretaciju ove definicije implementirat ćemo sami u prilagođenoj kuki. Svojstvo `initial` postavlja početno stanje na `idle`. Svojstvo `states` definira moguća stanja (`idle`, `loading` i `success`) i njihove prijelaze. Stanje `idle` ima svojstvo `on` koje definira prijelaz u stanje `loading` kada se pojavi događaj `CLICK`. Stanje `loading` koristi svojstvo `after` za automatski prijelaz u stanje `success` nakon 2000 milisekundi (2 sekunde). Stanje `success` je terminalno stanje u ovom primjeru.
2. Izradite prilagođenu kuku
Sada, izradimo prilagođenu kuku koja implementira logiku stroja stanja:
import { useState, useEffect } from 'react';
const useStateMachine = (stateMachineDefinition) => {
const [currentState, setCurrentState] = useState(stateMachineDefinition.initial);
const [context, setContext] = useState({});
const transition = (event) => {
const stateDefinition = stateMachineDefinition.states[currentState];
if (stateDefinition && stateDefinition.on && stateDefinition.on[event]) {
setCurrentState(stateDefinition.on[event]);
}
};
useEffect(() => {
const stateDefinition = stateMachineDefinition.states[currentState];
if (stateDefinition && stateDefinition.after) {
const timeoutKeys = Object.keys(stateDefinition.after);
timeoutKeys.forEach(timeoutKey => {
const timeout = parseInt(timeoutKey, 10);
const nextState = stateDefinition.after[timeoutKey];
const timer = setTimeout(() => {
setCurrentState(nextState);
clearTimeout(timer);
}, timeout);
return () => clearTimeout(timer); // Očisti na unmount ili promjenu stanja
});
}
}, [currentState, stateMachineDefinition.states]);
return {
currentState,
context,
transition,
};
};
export default useStateMachine;
Ova kuka `useStateMachine` uzima definiciju stroja stanja kao argument. Koristi `useState` za upravljanje trenutnim stanjem i kontekstom (kontekst ćemo objasniti kasnije). Funkcija `transition` uzima događaj kao argument i ažurira trenutno stanje na temelju definiranih prijelaza u definiciji stroja stanja. Kuka `useEffect` obrađuje svojstvo `after`, postavljajući tajmere za automatski prijelaz u sljedeće stanje nakon određenog trajanja. Kuka vraća trenutno stanje, kontekst i funkciju `transition`.
3. Upotrijebite prilagođenu kuku u komponenti
Na kraju, upotrijebimo prilagođenu kuku u React komponenti:
import React from 'react';
import useStateMachine from './useStateMachine';
const buttonStateMachineDefinition = {
initial: 'idle',
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success', // Nakon 2 sekunde, prijeđi u uspjeh
},
},
success: {},
},
};
const MyButton = () => {
const { currentState, transition } = useStateMachine(buttonStateMachineDefinition);
const handleClick = () => {
if (currentState === 'idle') {
transition('CLICK');
}
};
let buttonText = 'Kliknite me';
if (currentState === 'loading') {
buttonText = 'Učitavam...';
} else if (currentState === 'success') {
buttonText = 'Uspjeh!';
}
return (
);
};
export default MyButton;
Ova komponenta koristi kuku `useStateMachine` za upravljanje stanjem gumba. Funkcija `handleClick` šalje događaj `CLICK` kada se klikne gumb (i samo ako je u stanju `idle`). Komponenta prikazuje različit tekst na temelju trenutnog stanja. Gumb je onemogućen tijekom učitavanja kako bi se spriječilo višestruko klikanje.
Rukovanje kontekstom u strojevima stanja
U mnogim scenarijima iz stvarnog svijeta, strojevi stanja moraju upravljati podacima koji su trajni kroz prijelaze stanja. Ovi se podaci nazivaju kontekst. Kontekst vam omogućuje pohranu i ažuriranje relevantnih informacija kako stroj stanja napreduje.
Proširimo naš primjer gumba da uključimo brojač koji se povećava svaki put kada se gumb uspješno učita. Izmijenit ćemo definiciju stroja stanja i prilagođenu kuku za rukovanje kontekstom.
1. Ažurirajte definiciju stroja stanja
const buttonStateMachineDefinition = {
initial: 'idle',
context: {
count: 0,
},
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success',
},
},
success: {
entry: (context) => {
return { ...context, count: context.count + 1 };
},
},
},
};
Dodali smo svojstvo `context` u definiciju stroja stanja s početnom vrijednošću `count` od 0. Dodali smo i akciju `entry` u stanje `success`. Akcija `entry` se izvršava kada stroj stanja uđe u stanje `success`. Uzima trenutni kontekst kao argument i vraća novi kontekst s povećanim `count`. `entry` ovdje pokazuje primjer izmjene konteksta. Budući da se Javascript objekti prosljeđuju referencom, važno je vratiti *novi* objekt, a ne mutirati originalni.
2. Ažurirajte prilagođenu kuku
import { useState, useEffect } from 'react';
const useStateMachine = (stateMachineDefinition) => {
const [currentState, setCurrentState] = useState(stateMachineDefinition.initial);
const [context, setContext] = useState(stateMachineDefinition.context || {});
const transition = (event) => {
const stateDefinition = stateMachineDefinition.states[currentState];
if (stateDefinition && stateDefinition.on && stateDefinition.on[event]) {
setCurrentState(stateDefinition.on[event]);
}
};
useEffect(() => {
const stateDefinition = stateMachineDefinition.states[currentState];
if(stateDefinition && stateDefinition.entry){
const newContext = stateDefinition.entry(context);
setContext(newContext);
}
if (stateDefinition && stateDefinition.after) {
const timeoutKeys = Object.keys(stateDefinition.after);
timeoutKeys.forEach(timeoutKey => {
const timeout = parseInt(timeoutKey, 10);
const nextState = stateDefinition.after[timeoutKey];
const timer = setTimeout(() => {
setCurrentState(nextState);
clearTimeout(timer);
}, timeout);
return () => clearTimeout(timer); // Očisti na unmount ili promjenu stanja
});
}
}, [currentState, stateMachineDefinition.states, context]);
return {
currentState,
context,
transition,
};
};
export default useStateMachine;
Ažurirali smo kuku `useStateMachine` da inicijalizira stanje `context` s `stateMachineDefinition.context` ili praznim objektom ako kontekst nije naveden. Dodali smo i `useEffect` za rukovanje akcijom `entry`. Kada trenutno stanje ima akciju `entry`, izvršavamo je i ažuriramo kontekst s vraćenom vrijednošću.
3. Koristite ažuriranu kuku u komponenti
import React from 'react';
import useStateMachine from './useStateMachine';
const buttonStateMachineDefinition = {
initial: 'idle',
context: {
count: 0,
},
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success',
},
},
success: {
entry: (context) => {
return { ...context, count: context.count + 1 };
},
},
},
};
const MyButton = () => {
const { currentState, context, transition } = useStateMachine(buttonStateMachineDefinition);
const handleClick = () => {
if (currentState === 'idle') {
transition('CLICK');
}
};
let buttonText = 'Kliknite me';
if (currentState === 'loading') {
buttonText = 'Učitavam...';
} else if (currentState === 'success') {
buttonText = 'Uspjeh!';
}
return (
Brojač: {context.count}
);
};
export default MyButton;
Sada pristupamo `context.count` u komponenti i prikazujemo ga. Svaki put kada se gumb uspješno učita, brojač će se povećati.
Napredni koncepti strojeva stanja
Iako je naš primjer relativno jednostavan, strojevi stanja mogu se nositi sa znatno složenijim scenarijima. Evo nekih naprednih koncepata koje treba razmotriti:
- Čuvari: Uvjeti koji se moraju ispuniti da bi se dogodio prijelaz. Na primjer, prijelaz bi mogao biti dopušten samo ako je korisnik ovjeren ili ako određena vrijednost podataka premašuje prag.
- Akcije: Nuspojave koje se izvršavaju pri ulasku ili izlasku iz stanja. To bi moglo uključivati upućivanje API poziva, ažuriranje DOM-a ili slanje događaja drugim komponentama.
- Paralelna stanja: Omogućuju vam modeliranje sustava s više istodobnih aktivnosti. Na primjer, video player može imati jedan automat stanja za kontrole reprodukcije (reprodukcija, pauza, zaustavljanje) i drugi za upravljanje kvalitetom videa (niska, srednja, visoka).
- Hijerarhijska stanja: Omogućuju vam gniježđenje stanja unutar drugih stanja, stvarajući hijerarhiju stanja. To može biti korisno za modeliranje složenih sustava s mnogo povezanih stanja.
Alternativne biblioteke: XState i više
Iako naša prilagođena kuka pruža osnovnu implementaciju stroja stanja, nekoliko izvrsnih biblioteka može pojednostaviti proces i ponuditi naprednije značajke.
XState
XState je popularna JavaScript biblioteka za stvaranje, interpretaciju i izvršavanje strojeva stanja i dijagrama stanja. Pruža moćan i fleksibilan API za definiranje složenih strojeva stanja, uključujući podršku za čuvare, radnje, paralelna stanja i hijerarhijska stanja. XState također nudi izvrsne alate za vizualizaciju i otklanjanje pogrešaka u strojevima stanja.
Ostale biblioteke
Ostale opcije uključuju:
- Robot: Lagana biblioteka za upravljanje stanjem s fokusom na jednostavnost i performanse.
- react-automata: Biblioteka posebno dizajnirana za integraciju strojeva stanja u React komponente.
Izbor biblioteke ovisi o specifičnim potrebama vašeg projekta. XState je dobar izbor za kompleksne strojeve stanja, dok su Robot i react-automata prikladni za jednostavnije scenarije.
Najbolje prakse za korištenje strojeva stanja
Da biste učinkovito iskoristili strojeve stanja u svojim React aplikacijama, razmotrite sljedeće najbolje prakse:
- Počnite malo: Započnite s jednostavnim strojevima stanja i postupno povećavajte složenost po potrebi.
- Vizualizirajte svoj stroj stanja: Upotrijebite alate za vizualizaciju kako biste stekli jasno razumijevanje ponašanja vašeg stroja stanja.
- Napišite sveobuhvatne testove: Temeljito testirajte svako stanje i prijelaz kako biste osigurali da se vaš sustav ponaša ispravno.
- Dokumentirajte svoj stroj stanja: Jasno dokumentirajte stanja, prijelaze, čuvare i radnje vašeg stroja stanja.
- Razmotrite internacionalizaciju (i18n): Ako je vaša aplikacija usmjerena na globalnu publiku, osigurajte da su logika vašeg stroja stanja i korisničko sučelje pravilno internacionalizirani. Na primjer, koristite zasebne strojeve stanja ili kontekst za rukovanje različitim formatima datuma ili valutnim simbolima na temelju korisnikovog jezika.
- Pristupačnost (a11y): Osigurajte da su vaši prijelazi stanja i ažuriranja korisničkog sučelja dostupni korisnicima s invaliditetom. Upotrijebite ARIA atribute i semantički HTML kako biste pružili odgovarajući kontekst i povratne informacije pomoćnim tehnologijama.
Zaključak
React prilagođene kuke u kombinaciji sa strojevima stanja pružaju moćan i učinkovit pristup upravljanju kompleksnom logikom stanja u React aplikacijama. Apstrahiranjem prijelaza stanja i nuspojava u dobro definiran model, možete poboljšati organizaciju koda, smanjiti složenost, poboljšati testabilnost i povećati održivost. Bilo da implementirate vlastitu prilagođenu kuku ili koristite biblioteku poput XState, ugradnja strojeva stanja u vaš React tijek rada može značajno poboljšati kvalitetu i skalabilnost vaših aplikacija za korisnike širom svijeta.