Teraz, gdy znasz już podstawowe wzorce React, czas na zaawansowane techniki, które pozwolą Ci zarządzać złożonymi stanami i budować skalowalne systemy. W kosmicznej stacji -- to jak instalowanie zaawansowanych systemów sterowania!
Provider Pattern to rozwinięcie Context API. Tworzymy dedykowany provider, który hermetyzuje zarówno stan, jak i logikę:
1const ThemeContext = createContext();
2
3function ThemeProvider({ children }) {
4 const [theme, setTheme] = useState('dark');
5
6 const toggleTheme = () => {
7 setTheme(prev => prev === 'dark' ? 'light' : 'dark');
8 };
9
10 const value = useMemo(() => ({
11 theme, toggleTheme
12 }), [theme]);
13
14 return (
15 <ThemeContext.Provider value={value}>
16 <div className={`app-${theme}`}>
17 {children}
18 </div>
19 </ThemeContext.Provider>
20 );
21}
22
23// Custom hook dla konsumpcji
24function useTheme() {
25 const context = useContext(ThemeContext);
26 if (!context) {
27 throw new Error('useTheme must be used within ThemeProvider');
28 }
29 return context;
30}Gdy mamy wiele providerów, kompozycja jest kluczowa:
1// Zamiast zagnieżdżania:
2function App() {
3 return (
4 <ThemeProvider>
5 <AuthProvider>
6 <NotificationProvider>
7 <AppContent />
8 </NotificationProvider>
9 </AuthProvider>
10 </ThemeProvider>
11 );
12}
13
14// Możemy użyć compose pattern:
15function ComposeProviders({ providers, children }) {
16 return providers.reduceRight(
17 (acc, Provider) => <Provider>{acc}</Provider>,
18 children
19 );
20}
21
22function App() {
23 return (
24 <ComposeProviders
25 providers={[ThemeProvider, AuthProvider, NotificationProvider]}
26 >
27 <AppContent />
28 </ComposeProviders>
29 );
30}State machine eliminuje "niemożliwe stany" i gwarantuje, że aplikacja jest zawsze w jednym z zdefiniowanych stanów:
1// Stany i przejścia dla procesu startu
2const launchMachine = {
3 idle: {
4 START_PREFLIGHT: 'preflight'
5 },
6 preflight: {
7 CHECKS_PASSED: 'countdown',
8 CHECKS_FAILED: 'error'
9 },
10 countdown: {
11 COUNTDOWN_DONE: 'launching',
12 ABORT: 'aborted'
13 },
14 launching: {
15 LAUNCH_SUCCESS: 'inFlight',
16 LAUNCH_FAILURE: 'error'
17 },
18 inFlight: {},
19 error: {
20 RETRY: 'idle'
21 },
22 aborted: {
23 RETRY: 'idle'
24 }
25};
26
27function useMachine(machine, initialState) {
28 const [state, setState] = useState(initialState);
29
30 const send = useCallback((event) => {
31 setState(current => {
32 const nextState = machine[current]?.[event];
33 if (!nextState) {
34 console.warn(`No transition for ${event} in state ${current}`);
35 return current;
36 }
37 return nextState;
38 });
39 }, [machine]);
40
41 return [state, send];
42}
43
44// Użycie
45function LaunchControl() {
46 const [state, send] = useMachine(launchMachine, 'idle');
47
48 return (
49 <div className="launch-control">
50 <h2>Stan: {state}</h2>
51 {state === 'idle' && (
52 <button onClick={() => send('START_PREFLIGHT')}>
53 Rozpocznij procedury
54 </button>
55 )}
56 {state === 'preflight' && (
57 <>
58 <button onClick={() => send('CHECKS_PASSED')}>
59 Testy zaliczone
60 </button>
61 <button onClick={() => send('CHECKS_FAILED')}>
62 Testy niezdane
63 </button>
64 </>
65 )}
66 {state === 'countdown' && (
67 <button onClick={() => send('ABORT')}>ABORT!</button>
68 )}
69 {state === 'error' && (
70 <button onClick={() => send('RETRY')}>Ponów</button>
71 )}
72 </div>
73 );
74}Rozważmy typowy scenariusz bez state machine. Mamy komponent z kilkoma stanami boolowskimi:
1// BEZ state machine -- "niemożliwe stany" są możliwe
2const [isLoading, setIsLoading] = useState(false);
3const [isError, setIsError] = useState(false);
4const [isSuccess, setIsSuccess] = useState(false);
5
6// Co jeśli isLoading i isError są JEDNOCZEŚNIE true?
7// To "niemożliwy stan", ale nic nie stoi na przeszkodzie!Z state machine ten problem znika -- komponent jest ZAWSZE w dokładnie jednym stanie. Nie ma możliwości, żeby był jednocześnie w stanie
loading i error. To jak systemy bezpieczeństwa w statku kosmicznym -- nie możesz jednocześnie być w trybie lotu i trybie dokowania.| Sytuacja | Wzorzec | |----------|---------| | Współdzielenie danych w drzewie | Provider Pattern | | Wieloetapowe procesy | State Machines | | Formularz z walidacją | useForm hook + State Machine | | Lista z filtrowaniem | Custom hook + Presentational | | Komponent z wariantami | Composition + Props |
Provider Pattern i State Machines to zaawansowane narzędzia, które rozwiązują realne problemy w dużych aplikacjach React. Provider centralizuje dane i logikę, eliminując "prop drilling". State Machine porządkuje złożone przepływy stanów, gwarantując, że aplikacja nigdy nie znajdzie się w nieokreślonym stanie. Oba wzorce można łączyć -- na przykład Provider może udostępniać state machine do całego drzewa komponentów, dając każdemu widokowi dostęp do aktualnego stanu misji i funkcji przejścia.