


useReducer переносит правила изменения состояния в отдельную функцию. Компонент отправляет действие через dispatch, а reducer получает текущее состояние и действие, затем возвращает следующее состояние. Это удобно, когда несколько обработчиков должны соблюдать одни и те же правила.
На Mentoring.digital рассмотрим учебный выбор количества товара. Допустимы значения от 1 до 10, доступны увеличение, уменьшение и сброс. Никаких запросов к магазину пример не выполняет.
export function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, quantity: Math.min(10, state.quantity + 1) };
case "decrement":
return { ...state, quantity: Math.max(1, state.quantity - 1) };
case "reset":
return { quantity: 1 };
default:
throw new Error("Unknown action: " + action.type);
}
}
Reducer не изменяет исходный state. Он создаёт новый объект для следующего состояния. Ограничение количества находится в одном месте и действует независимо от того, какая кнопка или другое событие отправило действие.
Обработка неизвестного действия здесь выбрасывает ошибку, чтобы опечатка была заметна. В другом приложении допустимо вернуть прежнее состояние, если это согласованная политика. Главное, не оставлять случайную ветку без return.
import { useReducer } from "react";
import { reducer } from "./reducer.js";
export default function QuantityPicker() {
const [state, dispatch] = useReducer(reducer, { quantity: 1 });
return (
<section>
<p aria-live="polite">Количество: {state.quantity}</p>
<button onClick={() => dispatch({ type: "decrement" })}
disabled={state.quantity === 1}>Меньше</button>
<button onClick={() => dispatch({ type: "increment" })}
disabled={state.quantity === 10}>Больше</button>
<button onClick={() => dispatch({ type: "reset" })}>Сбросить</button>
</section>
);
}
Хук вызывается на верхнем уровне компонента. В обработчике передаётся функция, которая вызовет dispatch при нажатии. Запись onClick={dispatch(...)} вызвала бы его во время рендера и могла бы привести к циклу обновлений.
dispatch задаёт состояние для следующего рендера. Переменная state внутри уже выполняющегося обработчика остаётся снимком текущего рендера. Поэтому console.log сразу после dispatch может вывести прежнее количество. Это не доказывает, что обновление потерялось.
Мутация объекта с возвратом той же ссылки создаёт другую проблему: React может пропустить обновление, поскольку сравнивает состояние через Object.is. Для вложенного объекта нужно копировать и изменяемый вложенный уровень; поверхностное копирование не делает всё дерево независимым.
Запросы, сохранение в хранилище и другие побочные эффекты не должны быть частью reducer. При одинаковых аргументах он должен вычислять одинаковый результат. В Strict Mode React может дважды вызывать reducer и инициализатор в режиме разработки, чтобы обнаружить нечистую логику.
Проверьте увеличение с 1 до 2, невозможность выйти за пределы, сброс и неизменность исходного объекта. Отдельно проверьте интерфейс: доступность кнопок и отображение числа. Для единственного простого флага useState часто достаточно; useReducer полезен, когда централизованные правила делают код понятнее.
Практика компонентов доступна на курсах React. Спроектировать состояние формы или сложного экрана поможет ментор по React.