React useReducer

Хук useReducer предоставляет продвинутый способ управления состоянием в функциональных компонентах.

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

Если вы обнаруживаете, что отслеживаете несколько частей состояния, которые требуют сложной логики, то useReducer может быть полезным решением.


Особенности хука useReducer

  • Вместо простых установок состояния, как в useState, вы используете объект состояния и функцию-редюсер для его модификации.
  • Редюсер управляет изменениями состояния через действия (actions).
  • Отлично подходит для случаев, когда состояние сложное и включает большое число зависимых изменений.

Синтаксис

Хук useReducer принимает три аргумента:


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

  • reducer: функция, содержащая вашу пользовательскую логику состояния.
  • initialState: начальное состояние, обычно объект.
  • init: необязательный аргумент, используется для инициализации состояния.

Хук useReducer возвращает текущее состояние и метод dispatch.


Пример 1: Управление счётом игры

Представим простую игру, где игроки зарабатывают очки. Рассмотрим, как организовать управление такими действиями, как прибавление очков игроку и сброс игры.

Шаг 1: Определение начальных условий

Начнём с описания структуры игрока и стартового состояния игры:


const initialState = {
  players: [
    { id: 1, name: 'Alice', score: 0 },
    { id: 2, name: 'Bob', score: 0 },
  ],
  gameOver: false,
};

Шаг 2: Написание редюсера

Редюсер описывает логику изменения состояния на основе поступивших действий. Действия содержат тип операции и необходимые аргументы.


const gameReducer = (state, action) => {
  switch(action.type) {
    case 'ADD_SCORE':
      return {
        ...state,
        players: state.players.map(p => p.id === action.playerId ? { ...p, score: p.score + action.points } : p ),
      };
    case 'RESET_GAME':
      return {
        ...state,
        players: state.players.map(p => ({ ...p, score: 0 })),
        gameOver: false,
      };
    case 'GAME_OVER':
      return {
        ...state,
        gameOver: true,
      };
    default:
      return state;
  }
};

Шаг 3: Работа с состоянием в компоненте

Реализуем игровой компонент, который сможет увеличивать счёт игрока и сбрасывать игру:


import { useReducer } from 'react';

function Game() {
  const [gameState, dispatch] = useReducer(gameReducer, initialState);

  const increaseScore = (playerId, points) => {
    dispatch({ type: 'ADD_SCORE', playerId, points });
  };

  const resetGame = () => {
    dispatch({ type: 'RESET_GAME' });
  };

  const finishGame = () => {
    dispatch({ type: 'GAME_OVER' });
  };

  return (
    <div>
      <h1>Игра началась!</h1>
      {gameState.players.map(player => (
        <div key={player.id}>
          <span>{player.name}: {player.score} очков</span>
          <button onClick={() => increaseScore(player.id, 10)}>+10 баллов</button>
        </div>
      ))}
      <button onClick={resetGame}>Сбросить игру</button>
      <button onClick={finishGame}>Закончить игру</button>
    </div>
  );
}


Пример 2: Форма с несколькими полями


import React, { useReducer } from 'react';

const formReducer = (state, action) => {
  switch (action.type) {
    case 'INPUT_CHANGE':
      return {
        ...state,
        [action.field]: action.value,
      };
    case 'RESET':
      return { name: '', email: '', age: '' };
    default:
      return state;
  }
};

function UserForm() {
  const [formData, dispatch] = useReducer(formReducer, {
    name: '',
    email: '',
    age: '',
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    dispatch({
      type: 'INPUT_CHANGE',
      field: name,
      value,
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Данные формы:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Имя"
      />
      <input
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      <input
        name="age"
        value={formData.age}
        onChange={handleChange}
        type="number"
        placeholder="Возраст"
      />
      <button type="submit">Отправить</button>
      <button type="button" onClick={() => dispatch({ type: 'RESET' })}>
        Очистить
      </button>
    </form>
  );
}


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

СитуацияЛучше использовать
Простое состояние (число, строка, булево)useState
Состояние — объект с несколькими полямиuseReducer
Логика обновления сложная или зависит от предыдущего состоянияuseReducer
Нужно централизовать логику изменения состоянияuseReducer
Планируется масштабированиеuseReducer

Преимущества использования useReducer

  1. Четкая структура: Редюсеры делают логику изменения состояния понятной и управляемой.
  2. Масштабируемость: Хорошо подходит для больших объемов данных и сложного состояния.
  3. Производительность: Оптимизировано для минимизации рендеринга при частых изменениях состояния.