useReducer для сложного состояния
Моделируйте переходы состояния функцией-редьюсером, отправляйте объекты действий и предпочитайте useReducer вместо useState, когда логика состояния становится сложной.
«useReducer для сложного состояния» — бесплатный урок Frontend Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Frontend Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Frontend Academy содержит 4 уроков всего.
Когда useState недостаточно
Если переходы состояния включают сложную логику, несколько связанных значений или условия, при которых новое состояние нетривиальным образом зависит от предыдущего, useReducer будет более понятным выбором, чем несколько вызовов useState.
Шаблон функции-редьюсера
Редьюсер — это чистая функция: (state, action) => newState. Она принимает текущее состояние и объект действия, а возвращает следующее состояние. Без мутаций — всегда возвращайте новый объект.
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset'; payload: number };
function counterReducer(state: number, action: Action): number {
switch (action.type) {
case 'increment': return state + 1;
case 'decrement': return state - 1;
case 'reset': return action.payload;
default: return state;
}
}Синтаксис useReducer
useReducer(reducer, initialState) возвращает текущее состояние и функцию dispatch. Вызовите dispatch с объектом действия, чтобы запустить переход состояния.
function Counter() {
const [count, dispatch] = useReducer(counterReducer, 0);
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset', payload: 0 })}>Reset</button>
</div>
);
}Типизированные действия — дискриминируемые объединения
Используйте дискриминируемые объединения типов TypeScript для действий, чтобы получить исчерпывающую проверку типов в переключателе редьюсера.
type CartAction =
| { type: 'ADD_ITEM'; item: CartItem }
| { type: 'REMOVE_ITEM'; id: string }
| { type: 'SET_QUANTITY'; id: string; qty: number }
| { type: 'CLEAR' };Пример сложного состояния: корзина покупок
Корзина покупок с операциями добавления, удаления и изменения количества — отличный пример использования useReducer.
interface CartState {
items: CartItem[];
total: number;
}
function cartReducer(state: CartState, action: CartAction): CartState {
switch (action.type) {
case 'ADD_ITEM': {
const exists = state.items.find(i => i.id === action.item.id);
if (exists) {
const items = state.items.map(i =>
i.id === action.item.id ? { ...i, qty: i.qty + 1 } : i
);
return { ...state, items, total: calcTotal(items) };
}
const items = [...state.items, { ...action.item, qty: 1 }];
return { ...state, items, total: calcTotal(items) };
}
case 'CLEAR': return { items: [], total: 0 };
default: return state;
}
}Функция dispatch стабильна
Функция dispatch стабильна — она не изменяется между рендерингами. Её безопасно передавать через пропсы или использовать как зависимость useEffect, не вызывая повторных запусков.
Объединение useReducer и useContext
Мощный шаблон: контекст предоставляет и состояние, и dispatch, позволяя любому потребителю считывать состояние или отправлять действия без проброса пропсов.
const CartContext = createContext<{ state: CartState; dispatch: Dispatch<CartAction> } | null>(null);
function CartProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(cartReducer, { items: [], total: 0 });
return <CartContext.Provider value={{ state, dispatch }}>{children}</CartContext.Provider>;
}useReducer и useState
Выбирайте useReducer, когда: состояние имеет несколько подзначений, следующее состояние сложным образом зависит от предыдущего, переходов много и у них есть явные имена или вы хотите тестировать логику состояния отдельно от компонента.
Тестирование редьюсеров отдельно
Поскольку редьюсеры являются чистыми функциями, их очень просто тестировать без рендеринга какого-либо компонента.
test('increment action increases count', () => {
expect(counterReducer(0, { type: 'increment' })).toBe(1);
expect(counterReducer(5, { type: 'increment' })).toBe(6);
});
test('reset action sets count to payload', () => {
expect(counterReducer(42, { type: 'reset', payload: 0 })).toBe(0);
});Immer для неизменяемых обновлений
Библиотека immer позволяет писать код, похожий на изменяющий состояние, но создающий новое неизменяемое состояние. Это полезно для сложных обновлений вложенных объектов в редьюсерах.
Быстрая проверка
Как выглядит сигнатура функции-редьюсера?
Краткое повторение: useReducer
(state, action) => newState — чистая функция, без мутаций. useReducer(reducer, initial) возвращает [state, dispatch]. Для безопасности используйте типы действий в виде дискриминируемых объединений. dispatch стабилен. useReducer отлично подходит для сложного состояния с многочисленными переходами. Объединяйте его с useContext для управления глобальным состоянием. Тестируйте редьюсеры отдельно как чистые функции.
Часто задаваемые вопросы
Урок «useReducer для сложного состояния» бесплатный?
Да — полный текст урока «useReducer для сложного состояния» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Frontend Academy, подпишись на CoddyKit PRO. Курс Frontend Academy содержит 4 уроков всего.
Чему я научусь в уроке «useReducer для сложного состояния»?
Моделируйте переходы состояния функцией-редьюсером, отправляйте объекты действий и предпочитайте useReducer вместо useState, когда логика состояния становится сложной. Ты практикуешь Frontend Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Frontend Academy?
Предыдущий опыт не требуется. Frontend Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «useReducer для сложного состояния»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Frontend Academy?
Да. Каждый урок Frontend Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- useContext для глобального состояния
- useReducer для сложного состояния
- useMemo и useCallback для производительности
- Пользовательские хуки: выделение повторно используемой логики