Когда стоит использовать useReducer вместо useState?

Хук useReducer в React является альтернативой useState и предназначен для управления более сложными сценариями состояния. Он особенно полезен, когда логика обновления состояния включает множественные связанные переменные, сложные переходы между состояниями или зависит от предыдущего состояния. В отличие от useState, useReducer работает через диспетчеризацию действий (dispatch) и редьюсер-функцию, что делает его ближе к подходу, используемому в Redux.

Основы: как работает useReducer

const [state, dispatch] = useReducer(reducer, initialState);

  • reducer — функция, принимающая текущее состояние и action, возвращающая новое состояние.

  • initialState — начальное значение состояния.

  • state — текущее состояние.

  • dispatch — функция, с помощью которой мы отправляем действия, влияющие на состояние.

Пример:

function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
const initialState = { count: 0 };
function Counter() {
const \[state, dispatch\] = useReducer(reducer, initialState);
return (
<>
&lt;p&gt;{state.count}&lt;/p&gt;
&lt;button onClick={() =&gt; dispatch({ type: 'decrement' })}>-&lt;/button&gt;
&lt;button onClick={() =&gt; dispatch({ type: 'increment' })}>+&lt;/button&gt;
&lt;/&gt;
);
}

Когда стоит использовать useReducer вместо useState

1. Сложная логика обновления состояния

Если логика изменения состояния зависит от предыдущего состояния и включает множество условий, useReducer позволяет централизовать эту логику в одном месте.

useState:
setUser(prev => ({ ...prev, name: 'Alice' }));
useReducer:
dispatch({ type: 'update_name', payload: 'Alice' });

2. Множественные взаимосвязанные поля состояния

Когда состояние включает в себя несколько переменных, которые тесно связаны и изменяются в ответ на одни и те же действия.

const initialState = {
name: '',
email: '',
isSubmitted: false,
};

Вместо трёх useState — один useReducer.

3. Состояние требует сложных условий и вложенной логики

Например, в приложениях типа «мастер шагов» (step wizard), где переход между шагами зависит от предыдущего шага и пользовательского ввода.

switch (action.type) {
case 'next_step':
return { ...state, step: state.step + 1 };
case 'reset':
return initialState;
}

4. Необходимо передавать обновление состояния вглубь компонента

Если дочерние компоненты должны вызывать обновление состояния родителя, но ты не хочешь передавать кучу setState, можно передавать dispatch, как в Redux.

&lt;MyForm dispatch={dispatch} /&gt;

5. Нужна лучшая предсказуемость

Редьюсер — это чистая функция. Это упрощает отладку и делает поведение компонента более предсказуемым. Всё состояние меняется только через dispatch(action), нет случайных вызовов setState.

6. Интеграция с middleware-подобной логикой

Можно легко обернуть dispatch логикой, перехватывающей действия (например, логирование, асинхронность, side-effects и т.д.).

Когда не стоит использовать useReducer

Когда у тебя одно-два простых значения состояния, которые не связаны между собой.

```python
const [name, setName] = useState('');
const [age, setAge] = useState(0);

- Если обновление состояния **простое и линейное**, useState более читаемый и компактный.  

- Когда нет необходимости в **структурированной логике управления состоянием**, и dispatch/reducer выглядит как оверхед.  


### **Преимущества useReducer**

- Чистая и централизованная логика изменения состояния.  

- Упрощённая передача dispatch в дочерние компоненты (особенно в больших приложениях).  

- Возможность использовать шаблоны, похожие на Redux.  

- Удобно для unit-тестирования: редьюсер легко тестируется в отрыве от UI.  


### **Поддержка инициализации через lazy init**

useReducer поддерживает ленивая инициализация состояния через третий аргумент:

```python
function init(initialCount) {
return { count: initialCount };
}
const \[state, dispatch\] = useReducer(reducer, 0, init);

Это полезно, если начальное состояние сложно рассчитать.

Пример: управление формой

const initialState = {
name: '',
email: '',
error: null,
};
function reducer(state, action) {
switch (action.type) {
case 'change_field':
return { ...state, \[action.field\]: action.value };
case 'submit':
if (!state.name || !state.email) {
return { ...state, error: 'All fields required' };
}
return { ...state, error: null };
default:
return state;
}
}

Подходящая архитектура

Когда состояние и логика управления им разрастаются, имеет смысл:

  • Выносить reducer в отдельный файл

  • Использовать useContext + useReducer вместо Redux в небольших приложениях

  • Организовать тесты для reducer

Сравнение с useState

Характеристика useState useReducer
Простота Очень прост Немного сложнее
--- --- ---
Поддержка множественных значений Нужно несколько вызовов Можно объединить в один объект
--- --- ---
Централизация логики Разрозненно по setState В одном reducer
--- --- ---
Передача в дочерние компоненты Передаётся множество setX функций Одна dispatch функция
--- --- ---
Производительность Меньше абстракции Может быть быстрее при сложной логике
--- --- ---

Комбинирование useReducer с useContext

Это позволяет создать глобальный state management без Redux:

const AppStateContext = React.createContext();
const AppDispatchContext = React.createContext();
function AppProvider({ children }) {
const \[state, dispatch\] = useReducer(reducer, initialState);
return (
&lt;AppStateContext.Provider value={state}&gt;
&lt;AppDispatchContext.Provider value={dispatch}&gt;
{children}
&lt;/AppDispatchContext.Provider&gt;
&lt;/AppStateContext.Provider&gt;
);
}

Таким образом, состояние доступно в любом компоненте без проп-дриллинга.