← Назад к вопросам

Какие знаешь правила использования Reducer в Redux?

2.0 Middle🔥 221 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Основные правила использования Reducer в Redux

Reducer — это чистая функция, которая определяет, как состояние приложения изменяется в ответ на действия (actions). Соблюдение правил Redux необходимо для предсказуемости состояния, отладки и работы middleware (например, Redux DevTools).

1. Reducer должен быть чистой функцией

Это фундаментальное правило. Функция не должна:

  • Изменять аргументы, переданные ей (state и action).
  • Выполнять side effects (AJAX-запросы, изменение DOM, чтение/запись в localStorage).
  • Вызывать недетерминированные функции (Date.now(), Math.random()).

Пример правильного reducer:

// Корректно: возвращается новый объект состояния
const todoReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.text }];
    default:
      return state;
  }
};

Пример неправильного reducer (мутация состояния):

// Некорректно: прямое изменение state
const todoReducer = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      state.push({ id: Date.now(), text: action.text }); // Мутация!
      return state;
    default:
      return state;
  }
};

2. Reducer не должен изменять переданное состояние (state)

Вместо изменения существующего state, reducer должен возвращать новый объект состояния. Для обновления вложенных структур используется подход "copy-on-write".

Пример обновления вложенного состояния:

const initialState = {
  user: { name: 'John', age: 30 },
  tasks: ['task1', 'task2']
};

const appReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'UPDATE_USER_AGE':
      return {
        ...state, // Копируем верхний уровень
        user: {
          ...state.user, // Копируем объект user
          age: action.age // Обновляем нужное поле
        }
      };
    case 'ADD_TASK':
      return {
        ...state,
        tasks: [...state.tasks, action.task] // Новый массив
      };
    default:
      return state;
  }
};

3. Reducer должен обрабатывать состояние по умолчанию

При первом вызове Redux передает undefined в качестве state, поэтому необходимо предусмотреть значение по умолчанию.

// Значение по умолчанию через параметр по умолчанию
const counterReducer = (state = 0, action) => {
  // state будет равен 0 при первой загрузке
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    default:
      return state;
  }
};

4. Reducer должен возвращать исходное состояние для неизвестных action

Если reducer получает action, который он не должен обрабатывать, необходимо вернуть исходный state без изменений.

const visibilityReducer = (state = 'SHOW_ALL', action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter;
    default:
      return state; // Важно: возвращаем state без изменений
  }
};

5. Reducer не должен содержать сложную логику

Сложные вычисления и подготовку данных лучше выносить:

  • В action creators (особенно с использованием Redux Thunk).
  • В селекторы (библиотека Reselect).
  • В middleware.

Пример разделения ответственности:

// В action creator
export const fetchUserData = (userId) => async (dispatch) => {
  const response = await api.getUser(userId);
  dispatch({ type: 'USER_FETCHED', payload: response.data });
};

// В reducer - только обновление состояния
const userReducer = (state = null, action) => {
  switch (action.type) {
    case 'USER_FETCHED':
      return action.payload;
    default:
      return state;
  }
};

6. Композиция reducer'ов через combineReducers

Для управления разными частями состояния используются отдельные reducer'ы, которые комбинируются с помощью combineReducers.

import { combineReducers } from 'redux';

const todosReducer = (state = [], action) => {
  // логика для todos
};

const filterReducer = (state = 'SHOW_ALL', action) => {
  // логика для фильтра
};

const rootReducer = combineReducers({
  todos: todosReducer,
  visibilityFilter: filterReducer
});

// Итоговое состояние: { todos: [], visibilityFilter: 'SHOW_ALL' }

7. Reducer должен быть синхронным

Асинхронные операции (запросы к API, таймеры) должны выполняться в middleware (Redux Thunk, Redux Saga, Redux Observable), а не в reducer.

Практические рекомендации:

  • Используйте Immer для упрощения работы с неизменяемым состоянием.
  • Следите за производительностью: избегайте глубокого копирования больших объектов.
  • Придерживайтесь соглашений по именованию: action types как константы.
  • Пишите тесты для reducer'ов, так как они являются чистыми функциями.

Соблюдение этих правил обеспечивает предсказуемость работы приложения, возможность использования инструментов разработчика и простоту отладки. Нарушение любого из этих принципов может привести к трудноуловимым багам и проблемам с производительностью.