Utforska Reacts useActionState med tillstÄndsmaskiner för att bygga robusta och förutsÀgbara UI. LÀr dig logik för tillstÄndsövergÄngar i komplexa appar.
React useActionState TillstÄndsmaskin: BemÀstra logiken för tillstÄndsövergÄngar
Reacts useActionState
Àr en kraftfull hook som introducerades i React 19 (för nÀrvarande i canary) och Àr utformad för att förenkla asynkrona tillstÄndsuppdateringar, sÀrskilt vid hantering av server-actions. I kombination med en tillstÄndsmaskin ger den ett elegant och robust sÀtt att hantera komplexa UI-interaktioner och tillstÄndsövergÄngar. Detta blogginlÀgg kommer att djupdyka i hur man effektivt anvÀnder useActionState
med en tillstÄndsmaskin för att bygga förutsÀgbara och underhÄllbara React-applikationer.
Vad Àr en tillstÄndsmaskin?
En tillstÄndsmaskin Àr en matematisk berÀkningsmodell som beskriver beteendet hos ett system med ett Àndligt antal tillstÄnd och övergÄngar mellan dessa tillstÄnd. Varje tillstÄnd representerar ett distinkt lÀge i systemet, och övergÄngar representerar de hÀndelser som fÄr systemet att flytta frÄn ett tillstÄnd till ett annat. TÀnk pÄ det som ett flödesschema men med striktare regler för hur man kan röra sig mellan stegen.
Att anvÀnda en tillstÄndsmaskin i din React-applikation erbjuder flera fördelar:
- FörutsÀgbarhet: TillstÄndsmaskiner tvingar fram ett tydligt och förutsÀgbart kontrollflöde, vilket gör det lÀttare att resonera kring din applikations beteende.
- UnderhÄllbarhet: Genom att separera tillstÄndslogik frÄn UI-rendering förbÀttrar tillstÄndsmaskiner kodorganisationen och gör det lÀttare att underhÄlla och uppdatera din applikation.
- Testbarhet: TillstÄndsmaskiner Àr i sig testbara eftersom du enkelt kan definiera det förvÀntade beteendet för varje tillstÄnd och övergÄng.
- Visuell representation: TillstÄndsmaskiner kan representeras visuellt, vilket hjÀlper till att kommunicera applikationens beteende till andra utvecklare eller intressenter.
Introduktion till useActionState
useActionState
-hooken lÄter dig hantera resultatet av en handling som potentiellt Àndrar applikationens tillstÄnd. Den Àr utformad för att fungera sömlöst med server-actions, men kan Àven anpassas för klient-actions. Den erbjuder ett rent sÀtt att hantera laddningstillstÄnd, fel och det slutliga resultatet av en handling, vilket gör det lÀttare att bygga responsiva och anvÀndarvÀnliga UI:n.
HÀr Àr ett grundlÀggande exempel pÄ hur useActionState
anvÀnds:
const [state, dispatch] = useActionState(async (prevState, formData) => {
// Din handlingslogik hÀr
try {
const result = await someAsyncFunction(formData);
return { ...prevState, data: result };
} catch (error) {
return { ...prevState, error: error.message };
}
}, { data: null, error: null });
I detta exempel:
- Det första argumentet Àr en asynkron funktion som utför handlingen. Den tar emot det föregÄende tillstÄndet och formulÀrdata (om tillÀmpligt).
- Det andra argumentet Àr det initiala tillstÄndet.
- Hooken returnerar en array som innehÄller det nuvarande tillstÄndet och en dispatch-funktion.
Kombinera useActionState
och tillstÄndsmaskiner
Den verkliga kraften kommer frÄn att kombinera useActionState
med en tillstÄndsmaskin. Detta gör att du kan definiera komplexa tillstÄndsövergÄngar som utlöses av asynkrona handlingar. LÄt oss titta pÄ ett scenario: en enkel e-handelskomponent som hÀmtar produktinformation.
Exempel: HĂ€mta produktinformation
Vi kommer att definiera följande tillstÄnd för vÄr komponent för produktinformation:
- Inaktiv (Idle): Det initiala tillstÄndet. Ingen produktinformation har hÀmtats Àn.
- Laddar (Loading): TillstÄndet medan produktinformationen hÀmtas.
- Lyckad (Success): TillstÄndet efter att produktinformationen har hÀmtats framgÄngsrikt.
- Fel (Error): TillstÄndet om ett fel intrÀffade vid hÀmtning av produktinformationen.
Vi kan representera denna tillstÄndsmaskin med ett objekt:
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
Detta Àr en förenklad representation; bibliotek som XState erbjuder mer sofistikerade implementationer av tillstÄndsmaskiner med funktioner som hierarkiska tillstÄnd, parallella tillstÄnd och guards.
React-implementation
Nu ska vi integrera denna tillstÄndsmaskin med useActionState
i en React-komponent.
import React from 'react';
// Installera XState om du vill ha den fullstÀndiga tillstÄndsmaskinsupplevelsen. För detta grundlÀggande exempel anvÀnder vi ett enkelt objekt.
// import { createMachine, useMachine } from 'xstate';
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
function ProductDetails({ productId }) {
const [state, dispatch] = React.useReducer(
(state, event) => {
const nextState = productDetailsMachine.states[state].on[event];
return nextState || state; // Returnera nÀsta tillstÄnd eller det nuvarande om ingen övergÄng Àr definierad
},
productDetailsMachine.initial
);
const [productData, setProductData] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
if (state === 'loading') {
const fetchData = async () => {
try {
const response = await fetch(`https://api.example.com/products/${productId}`); // ErsÀtt med din API-slutpunkt
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setProductData(data);
setError(null);
dispatch('SUCCESS');
} catch (e) {
setError(e.message);
setProductData(null);
dispatch('ERROR');
}
};
fetchData();
}
}, [state, productId, dispatch]);
const handleFetch = () => {
dispatch('FETCH');
};
return (
Produktinformation
{state === 'idle' && }
{state === 'loading' && Laddar...
}
{state === 'success' && (
{productData.name}
{productData.description}
Pris: ${productData.price}
)}
{state === 'error' && Fel: {error}
}
);
}
export default ProductDetails;
Förklaring:
- Vi definierar
productDetailsMachine
som ett enkelt JavaScript-objekt som representerar vÄr tillstÄndsmaskin. - Vi anvÀnder
React.useReducer
för att hantera tillstÄndsövergÄngarna baserat pÄ vÄr maskin. - Vi anvÀnder Reacts
useEffect
-hook för att utlösa datahÀmtningen nÀr tillstÄndet Àr 'loading'. handleFetch
-funktionen skickar 'FETCH'-hÀndelsen, vilket initierar laddningstillstÄndet.- Komponenten renderar olika innehÄll beroende pÄ det aktuella tillstÄndet.
AnvÀnda useActionState
(Hypotetiskt - React 19-funktion)
Ăven om useActionState
Ànnu inte Àr fullt tillgÀnglig, Àr hÀr hur implementationen skulle se ut nÀr den blir tillgÀnglig, vilket erbjuder ett renare tillvÀgagÄngssÀtt:
import React from 'react';
//import { useActionState } from 'react'; // Avkommentera nÀr den Àr tillgÀnglig
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
function ProductDetails({ productId }) {
const initialState = { state: productDetailsMachine.initial, data: null, error: null };
// Hypotetisk implementation av useActionState
const [newState, dispatch] = React.useReducer(
(state, event) => {
const nextState = productDetailsMachine.states[state.state].on[event];
return nextState ? { ...state, state: nextState } : state; // Returnera nÀsta tillstÄnd eller det nuvarande om ingen övergÄng Àr definierad
},
initialState
);
const handleFetchProduct = async () => {
dispatch('FETCH');
try {
const response = await fetch(`https://api.example.com/products/${productId}`); // ErsÀtt med din API-slutpunkt
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// HĂ€mtning lyckades - skicka SUCCESS med datan!
dispatch('SUCCESS');
// Spara hÀmtad data i lokalt tillstÄnd. Kan inte anvÀnda dispatch inuti reducern.
newState.data = data; // Uppdatera utanför dispatchern
} catch (error) {
// Ett fel intrÀffade - skicka ERROR med felmeddelandet!
dispatch('ERROR');
// Lagra felet i en ny variabel för att visas i render()
newState.error = error.message;
}
//}, initialState);
};
return (
Produktinformation
{newState.state === 'idle' && }
{newState.state === 'loading' && Laddar...
}
{newState.state === 'success' && newState.data && (
{newState.data.name}
{newState.data.description}
Pris: ${newState.data.price}
)}
{newState.state === 'error' && newState.error && Fel: {newState.error}
}
);
}
export default ProductDetails;
Viktig notering: Detta exempel Àr hypotetiskt eftersom useActionState
inte Àr fullt tillgÀnglig Ànnu och dess exakta API kan komma att Àndras. Jag har ersatt den med standard-useReducer för att kÀrnlogiken ska fungera. Avsikten Àr dock att visa hur du *skulle* anvÀnda den, om den blir tillgÀnglig och du mÄste ersÀtta useReducer med useActionState. I framtiden med useActionState
bör denna kod fungera som förklarat med minimala Àndringar, vilket kraftigt förenklar den asynkrona datahanteringen.
Fördelar med att anvÀnda useActionState
med tillstÄndsmaskiner
- Tydlig ansvarsfördelning: TillstÄndslogiken Àr inkapslad i tillstÄndsmaskinen, medan UI-rendering hanteras av React-komponenten.
- FörbÀttrad kodlÀsbarhet: TillstÄndsmaskinen ger en visuell representation av applikationens beteende, vilket gör den lÀttare att förstÄ och underhÄlla.
- Förenklad asynkron hantering:
useActionState
effektiviserar hanteringen av asynkrona handlingar, vilket minskar mÀngden standardkod (boilerplate). - FörbÀttrad testbarhet: TillstÄndsmaskiner Àr i sig testbara, vilket gör att du enkelt kan verifiera att din applikations beteende Àr korrekt.
Avancerade koncept och övervÀganden
XState-integration
För mer komplexa behov av tillstÄndshantering, övervÀg att anvÀnda ett dedikerat bibliotek för tillstÄndsmaskiner som XState. XState erbjuder ett kraftfullt och flexibelt ramverk för att definiera och hantera tillstÄndsmaskiner, med funktioner som hierarkiska tillstÄnd, parallella tillstÄnd, guards och actions.
// Exempel med XState
import { createMachine, useMachine } from 'xstate';
const productDetailsMachine = createMachine({
id: 'productDetails',
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
invoke: {
id: 'fetchProduct',
src: (context, event) => fetch(`https://api.example.com/products/${context.productId}`).then(res => res.json()),
onDone: {
target: 'success',
actions: assign({ product: (context, event) => event.data })
},
onError: {
target: 'error',
actions: assign({ error: (context, event) => event.data })
}
}
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
}, {
services: {
fetchProduct: (context, event) => fetch(`https://api.example.com/products/${context.productId}`).then(res => res.json())
}
});
Detta ger ett mer deklarativt och robust sÀtt att hantera tillstÄnd. Se till att installera det med: npm install xstate
Global tillstÄndshantering
För applikationer med komplexa krav pÄ tillstÄndshantering över flera komponenter, övervÀg att anvÀnda en global lösning för tillstÄndshantering som Redux eller Zustand i kombination med tillstÄndsmaskiner. Detta gör att du kan centralisera din applikations tillstÄnd och enkelt dela det mellan komponenter.
Testa tillstÄndsmaskiner
Att testa tillstÄndsmaskiner Àr avgörande för att sÀkerstÀlla att din applikation Àr korrekt och tillförlitlig. Du kan anvÀnda testramverk som Jest eller Mocha för att skriva enhetstester för dina tillstÄndsmaskiner, och verifiera att de övergÄr mellan tillstÄnd som förvÀntat och hanterar olika hÀndelser korrekt.
HÀr Àr ett enkelt exempel:
// Exempel pÄ Jest-test
import { interpret } from 'xstate';
import { productDetailsMachine } from './productDetailsMachine';
describe('productDetailsMachine', () => {
it('should transition from idle to loading on FETCH event', (done) => {
const service = interpret(productDetailsMachine).onTransition((state) => {
if (state.value === 'loading') {
expect(state.value).toBe('loading');
done();
}
});
service.start();
service.send('FETCH');
});
});
Internationalisering (i18n)
NÀr man bygger applikationer för en global publik Àr internationalisering (i18n) avgörande. Se till att din tillstÄndsmaskinslogik och UI-rendering Àr korrekt internationaliserade för att stödja flera sprÄk och kulturella sammanhang. TÀnk pÄ följande:
- TextinnehÄll: AnvÀnd i18n-bibliotek för att översÀtta textinnehÄll baserat pÄ anvÀndarens sprÄkinstÀllningar (locale).
- Datum- och tidsformat: AnvÀnd bibliotek som Àr medvetna om sprÄkinstÀllningar för att visa datum och tider i rÀtt format för anvÀndarens region.
- Valutaformat: AnvÀnd bibliotek som Àr medvetna om sprÄkinstÀllningar för att visa valutavÀrden i rÀtt format för anvÀndarens region.
- Nummerformat: AnvÀnd bibliotek som Àr medvetna om sprÄkinstÀllningar för att visa siffror i rÀtt format för anvÀndarens region (t.ex. decimalavgrÀnsare, tusentalsavgrÀnsare).
- Höger-till-vÀnster (RTL) layout: Stöd RTL-layouter för sprÄk som arabiska och hebreiska.
Genom att beakta dessa i18n-aspekter kan du sÀkerstÀlla att din applikation Àr tillgÀnglig och anvÀndarvÀnlig för en global publik.
Sammanfattning
Att kombinera Reacts useActionState
med tillstĂ„ndsmaskiner erbjuder ett kraftfullt tillvĂ€gagĂ„ngssĂ€tt för att bygga robusta och förutsĂ€gbara anvĂ€ndargrĂ€nssnitt. Genom att separera tillstĂ„ndslogik frĂ„n UI-rendering och tvinga fram ett tydligt kontrollflöde förbĂ€ttrar tillstĂ„ndsmaskiner kodorganisation, underhĂ„llbarhet och testbarhet. Ăven om useActionState
fortfarande Àr en kommande funktion, kommer förstÄelsen för hur man integrerar tillstÄndsmaskiner nu att förbereda dig för att dra nytta av dess fördelar nÀr den blir tillgÀnglig. Bibliotek som XState erbjuder Ànnu mer avancerade funktioner för tillstÄndshantering, vilket gör det lÀttare att hantera komplex applikationslogik.
Genom att anamma tillstÄndsmaskiner och useActionState
kan du höja dina React-utvecklingsfÀrdigheter och bygga applikationer som Àr mer tillförlitliga, underhÄllbara och anvÀndarvÀnliga för anvÀndare över hela vÀrlden.