Odkrijte moč stanj strojev v Reactu s prilagojenimi hooki. Naučite se abstrahirati kompleksno logiko, izboljšati vzdržljivost kode in graditi robustne aplikacije.
React Custom Hook State Machine: Obvladovanje abstrakcije kompleksne logike stanja
Ker se aplikacije React povečujejo v kompleksnosti, lahko upravljanje stanja postane velik izziv. Tradicionalni pristopi z uporabo `useState` in `useEffect` lahko hitro pripeljejo do zapletene logike in težko vzdržljive kode, zlasti pri obravnavi zapletenih prehodov stanja in stranskih učinkov. Tukaj priskočijo na pomoč stanje stroji, še posebej React prilagojeni hooki, ki jih implementirajo. Ta članek vas bo vodil skozi koncept stanj strojev, pokazal, kako jih implementirati kot prilagojene hooke v Reactu, in ponazoril koristi, ki jih ponujajo za izgradnjo razširljivih in vzdržljivih aplikacij za globalno občinstvo.
Kaj je stanje stroj?
Stanje stroj (ali končni avtomat, FSM) je matematični model računanja, ki opisuje obnašanje sistema z določitvijo končnega števila stanj in prehodov med temi stanji. Predstavljajte si ga kot diagram poteka, vendar s strožjimi pravili in bolj formalno definicijo. Ključni koncepti vključujejo:
- Stanja: Predstavljajo različne pogoje ali faze sistema.
- Prehodi: Določajo, kako se sistem premika iz enega stanja v drugo na podlagi specifičnih dogodkov ali pogojev.
- Dogodki: Sprožilci, ki povzročijo prehode stanja.
- Začetno stanje: Stanje, v katerem se sistem začne.
Stanje stroji se odlikujejo pri modeliranju sistemov z dobro definiranimi stanji in jasnimi prehodi. Primerov je veliko v scenarijih iz resničnega sveta:
- Semaforske luči: Krožijo skozi stanja kot so Rdeča, Rumena, Zelena, s prehodi, ki jih sprožijo časovniki. To je globalno prepoznaven primer.
- Obdelava naročil: Naročilo e-trgovine lahko prehaja skozi stanja kot so "Čaka", "Obdelava", "Odpremljeno" in "Dostavljeno". To velja univerzalno za spletno trgovino.
- Potek avtentikacije: Postopek avtentikacije uporabnika lahko vključuje stanja kot so "Odjavljen", "Prijava", "Prijavljen" in "Napaka". Varnostni protokoli so na splošno dosledni v vseh državah.
Zakaj uporabljati stanje stroje v Reactu?
Integracija stanj strojev v vaše React komponente ponuja več prepričljivih prednosti:
- Izboljšana organizacija kode: Stanje stroji uveljavljajo strukturiran pristop k upravljanju stanja, zaradi česar je vaša koda bolj predvidljiva in lažje razumljiva. Nič več špageti kode!
- Zmanjšana kompleksnost: Z eksplicitnim definiranjem stanj in prehodov lahko poenostavite kompleksno logiko in se izognete nenamernim stranskim učinkom.
- Izboljšana preizkusnost: Stanje stroji so inherentno preizkusljivi. Z lahkoto lahko preverite, ali se vaš sistem obnaša pravilno, tako da preizkusite vsako stanje in prehod.
- Povečana vzdržljivost: Deklarativna narava stanj strojev omogoča lažje spreminjanje in razširjanje vaše kode, ko se vaša aplikacija razvija.
- Boljše vizualizacije: Obstajajo orodja, ki lahko vizualizirajo stanje stroje, kar omogoča jasen pregled nad obnašanjem vašega sistema, pomaga pri sodelovanju in razumevanju med ekipami z različnimi veščinami.
Implementacija stanja stroja kot React prilagojenega hooka
Ponazorimo, kako implementirati stanje stroj z uporabo React prilagojenega hooka. Ustvarili bomo preprost primer gumba, ki je lahko v treh stanjih: `idle` (mirovanje), `loading` (nalaganje) in `success` (uspeh). Gumb se začne v stanju `idle`. Ob kliku preide v stanje `loading`, simulira postopek nalaganja (z uporabo `setTimeout`), nato pa preide v stanje `success`.
1. Določite stanje stroj
Najprej določimo stanja in prehode našega gumba stanja stroja:
const buttonStateMachineDefinition = {
initial: 'idle',
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success', // Po 2 sekundah preide v uspeh
},
},
success: {},
},
};
Ta konfiguracija uporablja pristop, neodvisen od knjižnice (čeprav ga je navdihnil XState), za določitev stanja stroja. Logiko za interpretacijo te definicije bomo implementirali sami v prilagojenem hooku. Lastnost `initial` nastavi začetno stanje na `idle` (mirovanje). Lastnost `states` določa možna stanja (`idle`, `loading` in `success`) in njihove prehode. Stanje `idle` ima lastnost `on`, ki določa prehod v stanje `loading` (nalaganje), ko se zgodi dogodek `CLICK`. Stanje `loading` uporablja lastnost `after` za samodejni prehod v stanje `success` (uspeh) po 2000 milisekundah (2 sekundah). Stanje `success` je v tem primeru končno stanje.
2. Ustvarite prilagojeni hook
Sedaj pa ustvarimo prilagojeni hook, ki implementira logiko stanja stroja:
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); // Počisti ob odstranitvi komponente ali spremembi stanja
});
}
}, [currentState, stateMachineDefinition.states]);
return {
currentState,
context,
transition,
};
};
export default useStateMachine;
Ta `useStateMachine` hook sprejme definicijo stanja stroja kot argument. Uporablja `useState` za upravljanje trenutnega stanja in konteksta (kontekst bomo razložili kasneje). Funkcija `transition` sprejme dogodek kot argument in posodobi trenutno stanje na podlagi definiranih prehodov v definiciji stanja stroja. Hook `useEffect` obravnava lastnost `after`, nastavljajoč časovnike za samodejni prehod v naslednje stanje po določenem trajanju. Hook vrne trenutno stanje, kontekst in funkcijo `transition`.
3. Uporabite prilagojeni hook v komponenti
Končno, uporabimo prilagojeni hook v React komponenti:
import React from 'react';
import useStateMachine from './useStateMachine';
const buttonStateMachineDefinition = {
initial: 'idle',
states: {
idle: {
on: {
CLICK: 'loading',
},
},
loading: {
after: {
2000: 'success', // Po 2 sekundah preide v uspeh
},
},
success: {},
},
};
const MyButton = () => {
const { currentState, transition } = useStateMachine(buttonStateMachineDefinition);
const handleClick = () => {
if (currentState === 'idle') {
transition('CLICK');
}
};
let buttonText = 'Click Me';
if (currentState === 'loading') {
buttonText = 'Loading...';
} else if (currentState === 'success') {
buttonText = 'Success!';
}
return (
);
};
export default MyButton;
Ta komponenta uporablja `useStateMachine` hook za upravljanje stanja gumba. Funkcija `handleClick` sproži dogodek `CLICK`, ko je gumb kliknjen (in samo, če je v stanju `idle`). Komponenta prikaže različno besedilo glede na trenutno stanje. Gumb je med nalaganjem onemogočen, da prepreči večkratne klike.
Obravnavanje konteksta v stanjih strojev
V mnogih scenarijih iz resničnega sveta morajo stanje stroji upravljati podatke, ki ostanejo nespremenjeni skozi prehode stanja. Ti podatki se imenujejo kontekst. Kontekst vam omogoča shranjevanje in posodabljanje relevantnih informacij, ko stanje stroj napreduje.
Razširimo naš primer gumba tako, da vključuje števec, ki se poveča vsakič, ko se gumb uspešno naloži. Spremenili bomo definicijo stanja stroja in prilagojeni hook za obravnavanje konteksta.
1. Posodobite definicijo stanja stroja
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 lastnost `context` definiciji stanja stroja z začetno vrednostjo `count` 0. Dodali smo tudi `entry` akcijo v stanje `success`. Akcija `entry` se izvede, ko stanje stroj vstopi v stanje `success`. Sprejme trenutni kontekst kot argument in vrne nov kontekst s povečanim `count`. `entry` tukaj prikazuje primer spreminjanja konteksta. Ker se Javascript objekti prenašajo po referenci, je pomembno vrniti *nov* objekt namesto spreminjanja izvirnega.
2. Posodobite prilagojeni hook
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); // Počisti ob odstranitvi komponente ali spremembi stanja
});
}
}, [currentState, stateMachineDefinition.states, context]);
return {
currentState,
context,
transition,
};
};
export default useStateMachine;
Posodobili smo `useStateMachine` hook tako, da inicializira stanje `context` z `stateMachineDefinition.context` ali praznim objektom, če kontekst ni podan. Dodali smo tudi `useEffect` za obravnavanje `entry` akcije. Ko ima trenutno stanje `entry` akcijo, jo izvedemo in posodobimo kontekst z vrnjeno vrednostjo.
3. Uporabite posodobljeni hook v 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 = 'Click Me';
if (currentState === 'loading') {
buttonText = 'Loading...';
} else if (currentState === 'success') {
buttonText = 'Success!';
}
return (
Count: {context.count}
);
};
export default MyButton;
Sedaj dostopamo do `context.count` v komponenti in ga prikažemo. Vsakič, ko se gumb uspešno naloži, se bo števec povečal.
Napredni koncepti stanj strojev
Medtem ko je naš primer razmeroma preprost, lahko stanje stroji obravnavajo veliko bolj kompleksne scenarije. Tukaj je nekaj naprednih konceptov, ki jih je vredno upoštevati:
- Varovala (Guards): Pogoji, ki morajo biti izpolnjeni, da pride do prehoda. Na primer, prehod je lahko dovoljen samo, če je uporabnik avtenticiran ali če določena vrednost podatkov presega prag.
- Akcije (Actions): Stranski učinki, ki se izvedejo ob vstopu ali izstopu iz stanja. To lahko vključuje izvajanje API klicev, posodabljanje DOM-a ali pošiljanje dogodkov drugim komponentam.
- Vzporedna stanja (Parallel States): Omogočajo modeliranje sistemov z več sočasnimi aktivnostmi. Na primer, video predvajalnik ima lahko eno stanje stroj za nadzor predvajanja (predvajaj, pavza, ustavi) in drugo za upravljanje kakovosti videa (nizka, srednja, visoka).
- Hierarhična stanja (Hierarchical States): Omogočajo ugnezditev stanj znotraj drugih stanj, kar ustvarja hierarhijo stanj. To je lahko koristno za modeliranje kompleksnih sistemov z veliko povezanimi stanji.
Alternativne knjižnice: XState in več
Medtem ko naš prilagojeni hook zagotavlja osnovno implementacijo stanja stroja, lahko več odličnih knjižnic poenostavi postopek in ponudi naprednejše funkcije.
XState
XState je priljubljena JavaScript knjižnica za ustvarjanje, interpretacijo in izvajanje stanj strojev in statechartov. Zagotavlja zmogljiv in prilagodljiv API za določanje kompleksnih stanj strojev, vključno s podporo za varovala, akcije, vzporedna stanja in hierarhična stanja. XState ponuja tudi odlična orodja za vizualizacijo in odpravljanje napak v stanjih strojev.
Druge knjižnice
Druge možnosti vključujejo:
- Robot: Lahka knjižnica za upravljanje stanja s poudarkom na preprostosti in zmogljivosti.
- react-automata: Knjižnica, posebej zasnovana za integracijo stanj strojev v React komponente.
Izbira knjižnice je odvisna od specifičnih potreb vašega projekta. XState je dobra izbira za kompleksne stanje stroje, medtem ko sta Robot in react-automata primerna za enostavnejše scenarije.
Najboljše prakse za uporabo stanj strojev
Za učinkovito uporabo stanj strojev v vaših React aplikacijah upoštevajte naslednje najboljše prakse:
- Začnite z majhnim: Začnite s preprostimi stanji stroji in postopoma povečujte kompleksnost po potrebi.
- Vizualizirajte svoj stanje stroj: Uporabite orodja za vizualizacijo, da dobite jasno razumevanje obnašanja vašega stanja stroja.
- Napišite celovite teste: Temeljito preizkusite vsako stanje in prehod, da zagotovite, da se vaš sistem obnaša pravilno.
- Dokumentirajte svoj stanje stroj: Jasno dokumentirajte stanja, prehode, varovala in akcije vašega stanja stroja.
- Upoštevajte internacionalizacijo (i18n): Če je vaša aplikacija namenjena globalnemu občinstvu, zagotovite, da sta logika vašega stanja stroja in uporabniški vmesnik pravilno internacionalizirana. Na primer, uporabite ločene stanje stroje ali kontekst za obravnavanje različnih formatov datumov ali simbolov valut glede na lokalne nastavitve uporabnika.
- Dostopnost (a11y): Zagotovite, da so prehodi stanja in posodobitve uporabniškega vmesnika dostopni uporabnikom z invalidnostjo. Uporabite atribute ARIA in semantični HTML za zagotavljanje ustreznega konteksta in povratnih informacij pomožnim tehnologijam.
Zaključek
React prilagojeni hooki v kombinaciji s stanji stroji zagotavljajo zmogljiv in učinkovit pristop k upravljanju kompleksne logike stanja v aplikacijah React. Z abstrahiranjem prehodov stanja in stranskih učinkov v dobro definiran model lahko izboljšate organizacijo kode, zmanjšate kompleksnost, povečate preizkusnost in izboljšate vzdržljivost. Ne glede na to, ali implementirate svoj prilagojeni hook ali uporabite knjižnico, kot je XState, lahko vključitev stanj strojev v vaš delovni proces React bistveno izboljša kakovost in razširljivost vaših aplikacij za uporabnike po vsem svetu.