Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Typowanie Context API i useReducer

Context API to system komunikacji między odleglymi modułami statku kosmicznego - pozwala przesylac dane bez przekazywania ich przez każdy posredni komponent. TypeScript zapewnia, że te dane zawsze maja prawidłowy ksztalt.

Typowanie Context API

1import React, { createContext, useContext, useState } from 'react';
2
3// 1. Zdefiniuj typ kontekstu
4interface ThemeContextType {
5  theme: "dark" | "light";
6  toggleTheme: () => void;
7}
8
9// 2. Utworz kontekst z domyslna wartością
10const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
11
12// 3. Custom hook do uzycia kontekstu (z walidacja)
13function useTheme(): ThemeContextType {
14  const context = useContext(ThemeContext);
15  if (context === undefined) {
16    throw new Error("useTheme musi byc uzywany wewnatrz ThemeProvider");
17  }
18  return context;
19}
20
21// 4. Provider komponent
22function ThemeProvider({ children }: { children: React.ReactNode }) {
23  const [theme, setTheme] = useState<"dark" | "light">("dark");
24
25  const toggleTheme = () => {
26    setTheme(prev => prev === "dark" ? "light" : "dark");
27  };
28
29  return (
30    <ThemeContext.Provider value={{ theme, toggleTheme }}>
31      {children}
32    </ThemeContext.Provider>
33  );
34}

Dlaczego używać undefined jako domyslna wartość?

1// Wzorzec: createContext<T | undefined>(undefined)
2// + custom hook z walidacja
3
4// ZALETY:
5// - Blad w runtime jeśli komponent nie jest wewnątrz Providera
6// - Typ w komponencie jest zawsze ThemeContextType (nie undefined)
7// - Bezpieczniejsze niż podawanie "pustego" obiektu domyslnego
8
9// ALTERNATYWA (mniej bezpieczna):
10const ThemeContext = createContext<ThemeContextType>({
11  theme: "dark",
12  toggleTheme: () => {} // pusta funkcja - cichy błąd
13});

Typowanie useReducer

useReducer jest idealny do zarządzania zlozonym stanem. TypeScript sprawia, że każda akcja i zmiana stanu sa bezpieczne:

1// 1. Zdefiniuj typ stanu
2interface MissionState {
3  missions: Mission[];
4  selectedMission: Mission | null;
5  loading: boolean;
6  error: string | null;
7}
8
9interface Mission {
10  id: string;
11  name: string;
12  status: "planned" | "active" | "completed";
13}
14
15// 2. Zdefiniuj typy akcji - discriminated union!
16type MissionAction =
17  | { type: "SET_MISSIONS"; payload: Mission[] }
18  | { type: "SELECT_MISSION"; payload: string }
19  | { type: "ADD_MISSION"; payload: Mission }
20  | { type: "UPDATE_STATUS"; payload: { id: string; status: Mission["status"] } }
21  | { type: "SET_LOADING"; payload: boolean }
22  | { type: "SET_ERROR"; payload: string | null };
23
24// 3. Reducer z pelnym typowaniem
25function missionReducer(state: MissionState, action: MissionAction): MissionState {
26  switch (action.type) {
27    case "SET_MISSIONS":
28      return { ...state, missions: action.payload, loading: false };
29    case "SELECT_MISSION":
30      return {
31        ...state,
32        selectedMission: state.missions.find(m => m.id === action.payload) || null
33      };
34    case "ADD_MISSION":
35      return { ...state, missions: [...state.missions, action.payload] };
36    case "UPDATE_STATUS":
37      return {
38        ...state,
39        missions: state.missions.map(m =>
40          m.id === action.payload.id
41            ? { ...m, status: action.payload.status }
42            : m
43        )
44      };
45    case "SET_LOADING":
46      return { ...state, loading: action.payload };
47    case "SET_ERROR":
48      return { ...state, error: action.payload, loading: false };
49    default:
50      return state;
51  }
52}

Laczenie Context + useReducer

1// Stan początkowy
2const initialState: MissionState = {
3  missions: [],
4  selectedMission: null,
5  loading: false,
6  error: null
7};
8
9// Typ kontekstu
10interface MissionContextType {
11  state: MissionState;
12  dispatch: React.Dispatch<MissionAction>;
13}
14
15const MissionContext = createContext<MissionContextType | undefined>(undefined);
16
17// Custom hook
18function useMissions(): MissionContextType {
19  const context = useContext(MissionContext);
20  if (!context) {
21    throw new Error("useMissions musi byc uzywany wewnatrz MissionProvider");
22  }
23  return context;
24}
25
26// Provider
27function MissionProvider({ children }: { children: React.ReactNode }) {
28  const [state, dispatch] = useReducer(missionReducer, initialState);
29
30  return (
31    <MissionContext.Provider value={{ state, dispatch }}>
32      {children}
33    </MissionContext.Provider>
34  );
35}

Uzycie w komponentach

1function MissionList() {
2  const { state, dispatch } = useMissions();
3
4  if (state.loading) return <p>Ladowanie misji...</p>;
5  if (state.error) return <p>Blad: {state.error}</p>;
6
7  return (
8    <div>
9      {state.missions.map(mission => (
10        <div key={mission.id} onClick={() =>
11          dispatch({ type: "SELECT_MISSION", payload: mission.id })
12        }>
13          <h3>{mission.name}</h3>
14          <span>{mission.status}</span>
15        </div>
16      ))}
17    </div>
18  );
19}
Vai a CodeWorlds