Что такое useReducer и как его использовать?react-23

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

Основные понятия

  1. Редюсер (Reducer) — чистая функция, которая принимает предыдущее состояние и действие (action), возвращая новое состояние

    (state, action) => newState
    
  2. Действие (Action) — объект, описывающий что произошло, обычно содержит type и дополнительные данные

  3. Инициализатор (Initializer) — функция для ленивой инициализации начального состояния

Базовый синтаксис

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

Где:

  • reducer — функция-редюсер
  • initialArg — начальное состояние или аргумент для инициализатора
  • init (опционально) — функция инициализации

Простой пример

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      throw new Error('Unknown action type');
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

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

  1. Сложная логика состояния, когда следующее состояние зависит от предыдущего
  2. Большие объекты состояния, содержащие множество подзначений
  3. Оптимизация производительности — можно передавать dispatch вниз по дереву без колбэков
  4. Тестируемость — редюсеры являются чистыми функциями

Продвинутые паттерны

1. Ленивая инициализация

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

function Counter({ initialCount }) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  // ...
}

2. Имитация поведения Redux

// Действие с payload (полезной нагрузкой)
dispatch({
  type: 'ADD_TODO',
  payload: { id: 1, text: 'Learn useReducer' }
});

// Редюсер с обработкой payload
function todosReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, action.payload];
    // ...
  }
}

3. Комбинирование с useContext для глобального состояния

const StateContext = createContext();
const DispatchContext = createContext();

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        <ChildComponent />
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}

function ChildComponent() {
  const state = useContext(StateContext);
  const dispatch = useContext(DispatchContext);
  // ...
}

Практический пример

const formReducer = (state, action) => {
  switch (action.type) {
    case 'FIELD_CHANGE':
      return {
        ...state,
        [action.field]: action.value,
        isDirty: true
      };
    case 'SUBMIT':
      return { ...state, isSubmitting: true };
    case 'SUBMIT_SUCCESS':
      return { ...initialState, isSuccess: true };
    default:
      return state;
  }
};

function Form() {
  const [state, dispatch] = useReducer(formReducer, {
    username: '',
    email: '',
    isDirty: false,
    isSubmitting: false,
    isSuccess: false
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    dispatch({ type: 'SUBMIT' });
    // API call...
    dispatch({ type: 'SUBMIT_SUCCESS' });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={state.username}
        onChange={(e) => dispatch({
          type: 'FIELD_CHANGE',
          field: 'username',
          value: e.target.value
        })}
      />
      {/* другие поля */}
    </form>
  );
}

Оптимизация производительности

  1. Мемоизация dispatch — идентичность функции гарантируется React автоматически
  2. Разделение редюсеров для независимых частей состояния
  3. Пропуск промежуточных рендеров с помощью useMemo в дочерних компонентах

Резюмируем

useReducer — это мощная альтернатива useState для управления сложным состоянием в React-компонентах. Он обеспечивает предсказуемые обновления состояния через систему действий (actions) и редюсеров (reducers), что особенно полезно для сложной бизнес-логики. Хук идеально подходит для форм, сложных виджетов и глобального состояния приложения, особенно в комбинации с контекстом. Понимание useReducer важно для разработки масштабируемых React-приложений и является фундаментальным навыком профессионального React-разработчика.