Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Zaawansowane wzorce -- State Machines i Provider Pattern

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 -- Centralny system dystrybucji

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}

Kompozycja providerów

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 Machines -- Kontrolowane przejścia stanów

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}

Dlaczego State Machine jest lepsza niż boolean?

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.

Kiedy używać poszczególnych wzorców?

| 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 |

Podsumowanie

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.

Vai a CodeWorlds