Odkryj useActionState w React z maszynami stan贸w do tworzenia solidnych i przewidywalnych interfejs贸w. Poznaj logik臋 przej艣膰 stan贸w dla z艂o偶onych aplikacji.
Maszyna stan贸w z React useActionState: Opanowanie logiki przej艣膰 mi臋dzy stanami akcji
useActionState
w React to pot臋偶ny hak wprowadzony w React 19 (obecnie w wersji canary), zaprojektowany do upraszczania asynchronicznych aktualizacji stanu, zw艂aszcza w przypadku akcji serwerowych. W po艂膮czeniu z maszyn膮 stan贸w, zapewnia elegancki i solidny spos贸b zarz膮dzania z艂o偶onymi interakcjami UI i przej艣ciami mi臋dzy stanami. Ten wpis na blogu zag艂臋bi si臋 w to, jak efektywnie wykorzysta膰 useActionState
z maszyn膮 stan贸w do tworzenia przewidywalnych i 艂atwych w utrzymaniu aplikacji React.
Czym jest maszyna stan贸w?
Maszyna stan贸w to matematyczny model oblicze艅, kt贸ry opisuje zachowanie systemu jako sko艅czon膮 liczb臋 stan贸w i przej艣膰 mi臋dzy nimi. Ka偶dy stan reprezentuje odr臋bny warunek systemu, a przej艣cia reprezentuj膮 zdarzenia, kt贸re powoduj膮 przej艣cie systemu z jednego stanu do drugiego. Mo偶na o tym my艣le膰 jak o schemacie blokowym, ale z bardziej rygorystycznymi zasadami dotycz膮cymi przechodzenia mi臋dzy krokami.
U偶ywanie maszyny stan贸w w aplikacji React oferuje kilka korzy艣ci:
- Przewidywalno艣膰: Maszyny stan贸w wymuszaj膮 jasny i przewidywalny przep艂yw sterowania, co u艂atwia analizowanie zachowania aplikacji.
- 艁atwo艣膰 utrzymania: Oddzielaj膮c logik臋 stanu od renderowania interfejsu u偶ytkownika, maszyny stan贸w poprawiaj膮 organizacj臋 kodu i u艂atwiaj膮 jego utrzymanie i aktualizacj臋.
- Testowalno艣膰: Maszyny stan贸w s膮 z natury testowalne, poniewa偶 mo偶na 艂atwo zdefiniowa膰 oczekiwane zachowanie dla ka偶dego stanu i przej艣cia.
- Wizualna reprezentacja: Maszyny stan贸w mog膮 by膰 reprezentowane wizualnie, co pomaga w komunikowaniu zachowania aplikacji innym programistom lub interesariuszom.
Wprowadzenie do useActionState
Hak useActionState
pozwala obs艂ugiwa膰 wynik akcji, kt贸ra potencjalnie zmienia stan aplikacji. Zosta艂 zaprojektowany do p艂ynnej wsp贸艂pracy z akcjami serwerowymi, ale mo偶na go r贸wnie偶 dostosowa膰 do akcji po stronie klienta. Zapewnia czysty spos贸b zarz膮dzania stanami 艂adowania, b艂臋dami i ko艅cowym wynikiem akcji, u艂atwiaj膮c tworzenie responsywnych i przyjaznych dla u偶ytkownika interfejs贸w.
Oto podstawowy przyk艂ad u偶ycia useActionState
:
const [state, dispatch] = useActionState(async (prevState, formData) => {
// Twoja logika akcji tutaj
try {
const result = await someAsyncFunction(formData);
return { ...prevState, data: result };
} catch (error) {
return { ...prevState, error: error.message };
}
}, { data: null, error: null });
W tym przyk艂adzie:
- Pierwszym argumentem jest funkcja asynchroniczna, kt贸ra wykonuje akcj臋. Otrzymuje ona poprzedni stan oraz dane z formularza (je艣li dotyczy).
- Drugim argumentem jest stan pocz膮tkowy.
- Hak zwraca tablic臋 zawieraj膮c膮 bie偶膮cy stan i funkcj臋 dispatch.
艁膮czenie useActionState
i maszyn stan贸w
Prawdziwa moc pochodzi z po艂膮czenia useActionState
z maszyn膮 stan贸w. Pozwala to definiowa膰 z艂o偶one przej艣cia stan贸w wyzwalane przez akcje asynchroniczne. Rozwa偶my scenariusz: prosty komponent e-commerce, kt贸ry pobiera szczeg贸艂y produktu.
Przyk艂ad: Pobieranie szczeg贸艂贸w produktu
Zdefiniujemy nast臋puj膮ce stany dla naszego komponentu szczeg贸艂贸w produktu:
- Bezczynny (Idle): Stan pocz膮tkowy. Szczeg贸艂y produktu nie zosta艂y jeszcze pobrane.
- 艁adowanie (Loading): Stan podczas pobierania szczeg贸艂贸w produktu.
- Sukces (Success): Stan po pomy艣lnym pobraniu szczeg贸艂贸w produktu.
- B艂膮d (Error): Stan, je艣li wyst膮pi艂 b艂膮d podczas pobierania szczeg贸艂贸w produktu.
Mo偶emy przedstawi膰 t臋 maszyn臋 stan贸w za pomoc膮 obiektu:
const productDetailsMachine = {
initial: 'idle',
states: {
idle: {
on: {
FETCH: 'loading',
},
},
loading: {
on: {
SUCCESS: 'success',
ERROR: 'error',
},
},
success: {
type: 'final',
},
error: {
on: {
FETCH: 'loading',
},
},
},
};
To jest uproszczona reprezentacja; biblioteki takie jak XState oferuj膮 bardziej zaawansowane implementacje maszyn stan贸w z funkcjami takimi jak stany hierarchiczne, stany r贸wnoleg艂e i stra偶nicy (guards).
Implementacja w React
Teraz zintegrujmy t臋 maszyn臋 stan贸w z useActionState
w komponencie React.
import React from 'react';
// Zainstaluj XState, je艣li chcesz pe艂nego do艣wiadczenia z maszynami stan贸w. W tym podstawowym przyk艂adzie u偶yjemy prostego obiektu.
// 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; // Zwr贸膰 nast臋pny stan lub bie偶膮cy, je艣li nie zdefiniowano przej艣cia
},
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}`); // Zast膮p swoim punktem ko艅cowym API
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 (
Product Details
{state === 'idle' && }
{state === 'loading' && Loading...
}
{state === 'success' && (
{productData.name}
{productData.description}
Price: ${productData.price}
)}
{state === 'error' && Error: {error}
}
);
}
export default ProductDetails;
Wyja艣nienie:
- Definiujemy
productDetailsMachine
jako prosty obiekt JavaScript reprezentuj膮cy nasz膮 maszyn臋 stan贸w. - U偶ywamy
React.useReducer
do zarz膮dzania przej艣ciami stan贸w w oparciu o nasz膮 maszyn臋. - U偶ywamy haka
useEffect
z React, aby wyzwoli膰 pobieranie danych, gdy stan to 'loading'. - Funkcja
handleFetch
wysy艂a zdarzenie 'FETCH', inicjuj膮c stan 艂adowania. - Komponent renderuje r贸偶n膮 zawarto艣膰 w zale偶no艣ci od bie偶膮cego stanu.
U偶ycie useActionState
(Hipotetyczne - Funkcja React 19)
Chocia偶 useActionState
nie jest jeszcze w pe艂ni dost臋pny, oto jak wygl膮da艂aby implementacja, gdy ju偶 b臋dzie dost臋pna, oferuj膮c czystsze podej艣cie:
import React from 'react';
//import { useActionState } from 'react'; // Odkomentuj, gdy b臋dzie dost臋pne
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 };
// Hipotetyczna implementacja useActionState
const [newState, dispatch] = React.useReducer(
(state, event) => {
const nextState = productDetailsMachine.states[state.state].on[event];
return nextState ? { ...state, state: nextState } : state; // Zwr贸膰 nast臋pny stan lub bie偶膮cy, je艣li nie zdefiniowano przej艣cia
},
initialState
);
const handleFetchProduct = async () => {
dispatch('FETCH');
try {
const response = await fetch(`https://api.example.com/products/${productId}`); // Zast膮p swoim punktem ko艅cowym API
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Pomy艣lnie pobrano - wy艣lij SUCCESS z danymi!
dispatch('SUCCESS');
// Zapisz pobrane dane w stanie lokalnym. Nie mo偶na u偶ywa膰 dispatch wewn膮trz reducera.
newState.data = data; // Aktualizuj poza dispatcherem
} catch (error) {
// Wyst膮pi艂 b艂膮d - wy艣lij ERROR z komunikatem o b艂臋dzie!
dispatch('ERROR');
// Przechowaj b艂膮d w nowej zmiennej do wy艣wietlenia w render()
newState.error = error.message;
}
//}, initialState);
};
return (
Product Details
{newState.state === 'idle' && }
{newState.state === 'loading' && Loading...
}
{newState.state === 'success' && newState.data && (
{newState.data.name}
{newState.data.description}
Price: ${newState.data.price}
)}
{newState.state === 'error' && newState.error && Error: {newState.error}
}
);
}
export default ProductDetails;
Wa偶na uwaga: Ten przyk艂ad jest hipotetyczny, poniewa偶 useActionState
nie jest jeszcze w pe艂ni dost臋pny, a jego dok艂adne API mo偶e ulec zmianie. Zast膮pi艂em go standardowym useReducer, aby g艂贸wna logika mog艂a dzia艂a膰. Celem jest jednak pokazanie, jak *mo偶na by* go u偶y膰, gdy stanie si臋 dost臋pny i b臋dzie trzeba zast膮pi膰 useReducer przez useActionState. W przysz艂o艣ci, z useActionState
, ten kod powinien dzia艂a膰 zgodnie z wyja艣nieniem przy minimalnych zmianach, znacznie upraszczaj膮c obs艂ug臋 danych asynchronicznych.
Korzy艣ci z u偶ywania useActionState
z maszynami stan贸w
- Wyra藕ne rozdzielenie odpowiedzialno艣ci: Logika stanu jest zamkni臋ta w maszynie stan贸w, podczas gdy renderowaniem interfejsu zajmuje si臋 komponent React.
- Poprawiona czytelno艣膰 kodu: Maszyna stan贸w zapewnia wizualn膮 reprezentacj臋 zachowania aplikacji, co u艂atwia jej zrozumienie i utrzymanie.
- Uproszczona obs艂uga asynchroniczno艣ci:
useActionState
usprawnia obs艂ug臋 akcji asynchronicznych, redukuj膮c powtarzalny kod. - Lepsza testowalno艣膰: Maszyny stan贸w s膮 z natury testowalne, co pozwala 艂atwo zweryfikowa膰 poprawno艣膰 zachowania aplikacji.
Zaawansowane koncepcje i uwagi
Integracja z XState
Dla bardziej z艂o偶onych potrzeb zarz膮dzania stanem, rozwa偶 u偶ycie dedykowanej biblioteki maszyn stan贸w, takiej jak XState. XState dostarcza pot臋偶ne i elastyczne ramy do definiowania i zarz膮dzania maszynami stan贸w, z funkcjami takimi jak stany hierarchiczne, stany r贸wnoleg艂e, stra偶nicy (guards) i akcje.
// Przyk艂ad z u偶yciem 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())
}
});
Zapewnia to bardziej deklaratywny i solidny spos贸b zarz膮dzania stanem. Pami臋taj, aby zainstalowa膰 go za pomoc膮: npm install xstate
Globalne zarz膮dzanie stanem
Dla aplikacji o z艂o偶onych wymaganiach dotycz膮cych zarz膮dzania stanem w wielu komponentach, rozwa偶 u偶ycie globalnego rozwi膮zania do zarz膮dzania stanem, takiego jak Redux lub Zustand, w po艂膮czeniu z maszynami stan贸w. Pozwala to na centralizacj臋 stanu aplikacji i 艂atwe jego udost臋pnianie mi臋dzy komponentami.
Testowanie maszyn stan贸w
Testowanie maszyn stan贸w jest kluczowe, aby zapewni膰 poprawno艣膰 i niezawodno艣膰 aplikacji. Mo偶esz u偶ywa膰 framework贸w testowych, takich jak Jest czy Mocha, do pisania test贸w jednostkowych dla swoich maszyn stan贸w, weryfikuj膮c, czy przechodz膮 one mi臋dzy stanami zgodnie z oczekiwaniami i poprawnie obs艂uguj膮 r贸偶ne zdarzenia.
Oto prosty przyk艂ad:
// Przyk艂adowy test w Jest
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');
});
});
Internacjonalizacja (i18n)
Podczas tworzenia aplikacji dla globalnej publiczno艣ci, internacjonalizacja (i18n) jest niezb臋dna. Upewnij si臋, 偶e logika maszyny stan贸w i renderowanie interfejsu u偶ytkownika s膮 odpowiednio zinternacjonalizowane, aby wspiera膰 wiele j臋zyk贸w i kontekst贸w kulturowych. Rozwa偶 nast臋puj膮ce kwestie:
- Tre艣膰 tekstowa: U偶ywaj bibliotek i18n do t艂umaczenia tre艣ci tekstowych w oparciu o lokalizacj臋 u偶ytkownika.
- Formaty daty i czasu: U偶ywaj bibliotek formatuj膮cych dat臋 i czas z uwzgl臋dnieniem lokalizacji, aby wy艣wietla膰 daty i godziny w odpowiednim formacie dla regionu u偶ytkownika.
- Formaty walut: U偶ywaj bibliotek formatuj膮cych waluty z uwzgl臋dnieniem lokalizacji, aby wy艣wietla膰 warto艣ci pieni臋偶ne w odpowiednim formacie dla regionu u偶ytkownika.
- Formaty liczb: U偶ywaj bibliotek formatuj膮cych liczby z uwzgl臋dnieniem lokalizacji, aby wy艣wietla膰 liczby w odpowiednim formacie dla regionu u偶ytkownika (np. separatory dziesi臋tne, separatory tysi臋cy).
- Uk艂ad od prawej do lewej (RTL): Wspieraj uk艂ady RTL dla j臋zyk贸w takich jak arabski i hebrajski.
Bior膮c pod uwag臋 te aspekty i18n, mo偶esz zapewni膰, 偶e Twoja aplikacja b臋dzie dost臋pna i przyjazna dla u偶ytkownik贸w na ca艂ym 艣wiecie.
Podsumowanie
Po艂膮czenie useActionState
w React z maszynami stan贸w oferuje pot臋偶ne podej艣cie do tworzenia solidnych i przewidywalnych interfejs贸w u偶ytkownika. Poprzez oddzielenie logiki stanu od renderowania UI i wymuszanie jasnego przep艂ywu kontroli, maszyny stan贸w poprawiaj膮 organizacj臋 kodu, 艂atwo艣膰 utrzymania i testowalno艣膰. Chocia偶 useActionState
jest wci膮偶 nadchodz膮c膮 funkcj膮, zrozumienie, jak integrowa膰 maszyny stan贸w ju偶 teraz, przygotuje Ci臋 do wykorzystania jej zalet, gdy stanie si臋 dost臋pna. Biblioteki takie jak XState zapewniaj膮 jeszcze bardziej zaawansowane mo偶liwo艣ci zarz膮dzania stanem, u艂atwiaj膮c obs艂ug臋 z艂o偶onej logiki aplikacji.
Przyjmuj膮c maszyny stan贸w i useActionState
, mo偶esz podnie艣膰 swoje umiej臋tno艣ci programistyczne w React i tworzy膰 aplikacje, kt贸re s膮 bardziej niezawodne, 艂atwiejsze w utrzymaniu i przyjazne dla u偶ytkownik贸w na ca艂ym 艣wiecie.