Skip to main content

Що таке патерн "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> ); }

Що відбувається "під капотом"

  1. useReducer керує локальним станом.
  2. Context.Provider робить { state, dispatch } доступним глобально.
  3. Будь-який компонент у дереві може:
  • читати поточний стан через 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

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.