Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

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
3React вызывает todosReducer(todos, action)
4Редьюсер возвращает новый массив
5React перерисовывает компонент с новым состоянием

Редьюсер легко проверить

Редьюсер не зависит от React, поэтому его можно вызвать как обычную функцию — например, в тесте: todosReducer([], { type: 'added', text: 'Тест' }) вернёт массив из одной задачи.

useState или useReducer

useStateuseReducer
Одно-два простых значенияОбъект или массив с многими операциями
Логика изменения в одну строкуЛогика стоит собрать в одном месте
Счётчик, флажок, текст поляКорзина, список задач, многошаговая форма

Частая ошибка

Редьюсер должен быть чистым: только вычислять новое состояние по старому и действию. Запросы к серверу, console.log с побочными целями или изменение переданного массива внутри него приводят к трудноуловимым ошибкам:

// src/todosReducer.js
// Неправильно: массив меняется на месте.
case 'added':
  todos.push({ id: crypto.randomUUID(), text: action.text, done: false });
  return todos;

Возвращайте новый массив, а запросы выполняйте в обработчиках событий или эффектах.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.