Що таке патерн "Context + Reducer"?
Суть патерна
Поєднуємо Context (для глобального доступу до даних) і useReducer (для передбачуваного керування станом через дії).
Context - це "спосіб поділитися станом". Reducer - це "мозок", який каже, як стан змінюється.
Базова ідея
javascript
// Context: доступний усім компонентам
const CounterContext = createContext();
// Reducer: керує змінами state
function counterReducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error("Unknown action");
}
}Реалізація патерна
1. Provider (обгортає весь застосунок)
javascript
function CounterProvider({ children }) {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
}2. Використання в компонентах
javascript
function CounterDisplay() {
const { state } = useContext(CounterContext);
return <p>Count: {state.count}</p>;
}
function CounterButtons() {
const { dispatch } = useContext(CounterContext);
return (
<>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
}3. Обгортаємо застосунок
javascript
function App() {
return (
<CounterProvider>
<CounterDisplay />
<CounterButtons />
</CounterProvider>
);
}Що відбувається "під капотом"
useReducerкерує локальним станом.Context.Providerробить{ state, dispatch }доступним глобально.- Будь-який компонент у дереві може:
- читати поточний стан через
useContext(Context).state; - змінювати його через
dispatch({ type: "..." }).
Чому це зручно
| Перевага | Опис |
|---|---|
| Передбачуваність | Усі зміни централізовані через reducer |
| Глобальна доступність | Через Context стан доступний усьому дереву |
| Масштабованість | Можна створювати багато ізольованих "сторів" |
| Простота | Не потрібен Redux, Redux Toolkit, Zustand тощо |
| Тестованість | reducer - чиста функція, легко тестується |
| Інкапсуляція | Стан керується строго через dispatch |
Просунута версія (розділення state і dispatch)
Щоб уникнути зайвих ререндерів, контексти часто розділяють:
javascript
const StateContext = createContext();
const DispatchContext = createContext();
function CounterProvider({ children }) {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</DispatchContext.Provider>
);
}
// тепер споживачі state і dispatch незалежні:
function useCounterState() {
return useContext(StateContext);
}
function useCounterDispatch() {
return useContext(DispatchContext);
}Тепер зміна state не викликає ререндер у компонентів,
які підписані лише на dispatch.
Де застосовується
- Auth: логін/логаут/токен
- Theme: темна/світла тема
- Cart: кошик в e-commerce
- UI: модалки, алерти, лоадери
- Form Wizard: переходи між кроками
Підсумок
| Що дає | Як реалізується |
|---|---|
| Глобальний доступ до стану | Context |
| Передбачувані оновлення | useReducer |
| Централізована логіка | reducer-функції |
| Проста архітектура без Redux | поєднання Context + Reducer |
Висновок:
"Context + Reducer" - це міні-Redux, вбудований у React: простий, чистий, і ідеально підходить для середнього рівня складності застосунку.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.