Хук 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
- Четкая структура: Редюсеры делают логику изменения состояния понятной и управляемой.
- Масштабируемость: Хорошо подходит для больших объемов данных и сложного состояния.
- Производительность: Оптимизировано для минимизации рендеринга при частых изменениях состояния.