Что такое useReducer в React?

MiddleReact · Frontend·Обновлено 10 июля 2026
Коротко
useReducer — хук React для управления сложным состоянием компонента через функцию-редюсер, аналогично паттерну Redux. Принимает редюсер и начальное состояние, возвращает текущее состояние и функцию dispatch для отправки действий.

Что такое useReducer

useReducer — встроенный хук React, который позволяет управлять состоянием компонента через концепцию редюсера. Это альтернатива useState для случаев, когда логика обновления состояния сложна или следующее состояние зависит от предыдущего.

Сигнатура

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer — чистая функция (state, action) => newState
  • initialState — начальное значение состояния
  • state — текущее состояние
  • dispatch — функция для отправки действий

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

  1. Состояние — это объект с несколькими взаимосвязанными полями
  2. Следующее состояние зависит от предыдущего
  3. Логика обновления состояния сложная и её нужно вынести отдельно
  4. Несколько обработчиков событий выполняют похожие изменения состояния

Структура редюсера

Редюсер — чистая функция: не мутирует состояние, не вызывает побочных эффектов, при одинаковых входных данных всегда возвращает одинаковый результат.

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 принимает необязательный третий аргумент — функцию init, которая вычисляет начальное состояние лениво. Полезно, если вычисление начального значения дорогостоящее:

function init(initialCount: number) {
  return { count: initialCount };
}

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

Сравнение с useState

Критерий useState useReducer
Простое скалярное значение Предпочтителен Избыточен
Объект с несколькими полями Возможен Предпочтителен
Сложная логика переходов Труден в поддержке Хорошо структурирует
Тестируемость логики Тестируется внутри компонента Редюсер тестируется отдельно

Связка с useContext

Один из популярных паттернов — передавать dispatch через контекст, чтобы любой дочерний компонент мог отправлять действия без prop drilling:

const DispatchContext = createContext<React.Dispatch<Action> | null>(null);

function Parent() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <DispatchContext.Provider value={dispatch}>
      <Child />
    </DispatchContext.Provider>
  );
}

Этот паттерн является облегчённой заменой Redux для локального состояния приложения.

Что хочет услышать интервьюер

Кандидат понимает разницу между useState и useReducer и умеет объяснить, в каких случаях предпочтительнее каждый из них

Знание структуры редюсера: чистая функция (state, action) => newState, типизация action через дискриминированные union-типы

Понимание того, что редюсер должен быть чистой функцией — без мутаций и побочных эффектов

Осведомлённость о ленивой инициализации через третий аргумент

Знание паттерна связки useReducer + useContext как альтернативы Redux

Пример: Управление формой с useReducer

import { useReducer } from 'react';

interface FormState {
  username: string;
  email: string;
  isSubmitting: boolean;
  error: string | null;
}

type FormAction =
  | { type: 'setField'; field: keyof Pick<FormState, 'username' | 'email'>; value: string }
  | { type: 'submit' }
  | { type: 'submitSuccess' }
  | { type: 'submitError'; message: string };

const initialState: FormState = {
  username: '',
  email: '',
  isSubmitting: false,
  error: null,
};

function formReducer(state: FormState, action: FormAction): FormState {
  switch (action.type) {
    case 'setField':
      // Обновляем только одно поле, остальные сохраняем
      return { ...state, [action.field]: action.value, error: null };
    case 'submit':
      return { ...state, isSubmitting: true, error: null };
    case 'submitSuccess':
      return { ...initialState };
    case 'submitError':
      return { ...state, isSubmitting: false, error: action.message };
    default:
      return state;
  }
}

export function RegistrationForm() {
  const [state, dispatch] = useReducer(formReducer, initialState);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    dispatch({ type: 'submit' });

    try {
      await fetch('/api/register', {
        method: 'POST',
        body: JSON.stringify({ username: state.username, email: state.email }),
      });
      dispatch({ type: 'submitSuccess' });
    } catch {
      dispatch({ type: 'submitError', message: 'Ошибка регистрации' });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={state.username}
        onChange={e => dispatch({ type: 'setField', field: 'username', value: e.target.value })}
        placeholder="Имя пользователя"
      />
      <input
        value={state.email}
        onChange={e => dispatch({ type: 'setField', field: 'email', value: e.target.value })}
        placeholder="Email"
      />
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}
      <button type="submit" disabled={state.isSubmitting}>
        {state.isSubmitting ? 'Отправка...' : 'Зарегистрироваться'}
      </button>
    </form>
  );
}

Типичные ошибки

Мутация состояния внутри редюсера вместо возврата нового объекта

Использование useReducer для простых случаев, где достаточно useState — излишнее усложнение

Отсутствие case default в switch, что может привести к возврату undefined при неизвестном action.type

Размещение побочных эффектов (fetch, таймеры) внутри редюсера вместо useEffect

Неправильная типизация action: использование any вместо дискриминированных union-типов TypeScript

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее