useReducer
Когда у состояния много способов измениться — добавить, удалить, отметить, очистить, — функции-обработчики расползаются по компоненту. Хук useReducer собирает всю эту логику в одну функцию — редьюсер. Компонент лишь сообщает, что произошло, отправляя действие: объект вида { type: 'added', text: '...' }.
Редьюсер
Редьюсер — обычная функция без React: принимает текущее состояние и действие, возвращает новое состояние. Создайте src/todosReducer.js:
// src/todosReducer.js
export function todosReducer(todos, action) {
switch (action.type) {
case 'added':
return [...todos, { id: crypto.randomUUID(), text: action.text, done: false }];
case 'toggled':
return todos.map((todo) =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo,
);
case 'deleted':
return todos.filter((todo) => todo.id !== action.id);
default:
throw new Error('Неизвестное действие: ' + action.type);
}
}Правила те же, что для useState: массив не меняют на месте, а возвращают новый.
Подключение в компоненте
useReducer принимает редьюсер и начальное состояние, а возвращает состояние и функцию dispatch для отправки действий:
// src/App.jsx
import { useReducer } from 'react';
import { todosReducer } from './todosReducer.js';
import AddTodoForm from './components/AddTodoForm.jsx';
import TodoList from './components/TodoList.jsx';
const initialTodos = [{ id: 1, text: 'Купить молоко', done: false }];
export default function App() {
const [todos, dispatch] = useReducer(todosReducer, initialTodos);
return (
<>
<AddTodoForm onAdd={(text) => dispatch({ type: 'added', text })} />
<TodoList
todos={todos}
onToggle={(id) => dispatch({ type: 'toggled', id })}
onDelete={(id) => dispatch({ type: 'deleted', id })}
/>
</>
);
}Страница работает точно так же, как с useState: задачи добавляются, отмечаются и удаляются. Но вся логика теперь в одном файле.
Как это работает
| Шаг | Что происходит |
|---|---|
| 1 | Пользователь нажимает «Удалить» |
| 2 | Компонент вызывает dispatch с действием deleted |
| 3 | React вызывает todosReducer(todos, action) |
| 4 | Редьюсер возвращает новый массив |
| 5 | React перерисовывает компонент с новым состоянием |
Редьюсер легко проверить
Редьюсер не зависит от React, поэтому его можно вызвать как обычную функцию — например, в тесте: todosReducer([], { type: 'added', text: 'Тест' }) вернёт массив из одной задачи.
useState или useReducer
useState | useReducer |
|---|---|
| Одно-два простых значения | Объект или массив с многими операциями |
| Логика изменения в одну строку | Логика стоит собрать в одном месте |
| Счётчик, флажок, текст поля | Корзина, список задач, многошаговая форма |
Частая ошибка
Редьюсер должен быть чистым: только вычислять новое состояние по старому и действию. Запросы к серверу, console.log с побочными целями или изменение переданного массива внутри него приводят к трудноуловимым ошибкам:
// src/todosReducer.js
// Неправильно: массив меняется на месте.
case 'added':
todos.push({ id: crypto.randomUUID(), text: action.text, done: false });
return todos;Возвращайте новый массив, а запросы выполняйте в обработчиках событий или эффектах.