Когда стоит использовать 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 (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
);
}
Когда стоит использовать 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.
<MyForm dispatch={dispatch} />
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 (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
}
Таким образом, состояние доступно в любом компоненте без проп-дриллинга.