В чем разница между useState и useReducer?
Хуки
Оба хука хранят состояние; разница — в способе обновления: useState — прямые сеттеры, useReducer — централизованная функция-редьюсер, обрабатывающая действия.
useState
const [count, setCount] = useState(0);
setCount(count + 1);Минимум кода; логика обновления живёт в местах вызова сеттера. Идеален для независимых простых значений: флаги, поля ввода, выбранный таб.
useReducer
const reducer = (state, action) => {
switch (action.type) {
case 'add': return { ...state, items: [...state.items, action.item] };
case 'remove': return { ...state, items: state.items.filter((i) => i.id !== action.id) };
case 'clear': return { ...state, items: [] };
default: return state;
}
};
const [state, dispatch] = useReducer(reducer, { items: [] });
dispatch({ type: 'add', item });Все переходы состояния собраны в одной чистой функции; компоненты лишь сообщают что произошло.
Когда выбирать useReducer
- Несколько полей меняются согласованно (форма-визард, корзина).
- Следующее состояние зависит от предыдущего сложным образом.
- Одни и те же переходы дёргаются из многих мест — логика не дублируется.
- Нужно тестировать логику отдельно: reducer — чистая функция.
- dispatch стабилен — его можно передавать вглубь без useCallback.
Когда useState
Всё простое и независимое. Три-четыре несвязанных useState лучше одного «объекта всего» — обновления точнее, код проще.
Нюансы
- useState внутри реализован через useReducer с базовым редьюсером.
- useReducer + Context — лёгкий «Redux без Redux» для поддерева.
Коротко для собеседования
useState — прямые сеттеры, логика в обработчиках; для простых независимых значений. useReducer — переходы в одной чистой функции, компоненты диспатчат действия; для сложного взаимосвязанного состояния, тестируемости и стабильного dispatch. Правило: стало сложно согласовывать несколько setX — пора в reducer.