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

Возвращает ли Reducer новое состояние, или меняет старое

2.0 Middle🔥 242 комментариев
#React#State Management

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Возвращает ли Reducer новое состояние, или меняет старое

Один из ключевых концептов в современной разработке с использованием React, Redux и других state management решений — это правило неизменяемости (immutability). Reducer ВСЕГДА должен возвращать НОВОЕ состояние, а не изменять старое.

Основной принцип

Reducer должен быть pure function (чистой функцией):

  • Не должен мутировать (менять) входное состояние
  • Должен возвращать новый объект состояния
  • Всегда возвращает одинаковый результат для одинаковых входных параметров
  • Не имеет побочных эффектов (side effects)

Пример с неправильным подходом (мутация)

// ПЛОХО! Мутация состояния
function userReducer(state, action) {
  switch (action.type) {
    case 'UPDATE_NAME':
      state.name = action.payload; // прямое изменение старого состояния
      return state; // возвращаем тот же объект
      
    case 'INCREMENT_POSTS':
      state.posts++; // мутация
      return state;
      
    default:
      return state;
  }
}

const initialState = { name: 'Иван', posts: 5 };
let state = initialState;

state = userReducer(state, { type: 'UPDATE_NAME', payload: 'Петр' });
state = userReducer(state, { type: 'INCREMENT_POSTS' });

// Проблема: исходный объект изменился!
console.log(initialState.name); // 'Петр' (изменился!)

Пример с правильным подходом (новое состояние)

// ХОРОШО! Возвращаем новое состояние
function userReducer(state, action) {
  switch (action.type) {
    case 'UPDATE_NAME':
      return {
        ...state, // копируем все свойства
        name: action.payload // переопределяем только name
      };
      
    case 'INCREMENT_POSTS':
      return {
        ...state,
        posts: state.posts + 1
      };
      
    default:
      return state;
  }
}

const initialState = { name: 'Иван', posts: 5 };
let state = initialState;

state = userReducer(state, { type: 'UPDATE_NAME', payload: 'Петр' });
state = userReducer(state, { type: 'INCREMENT_POSTS' });

// Исходный объект не изменился
console.log(initialState.name); // 'Иван' (не изменился)
console.log(state.name); // 'Петр'
console.log(state.posts); // 6
console.log(initialState === state); // false - разные объекты

Почему это важно?

1. React зависит от неизменяемости для оптимизации

import { useReducer } from 'react';

function Component() {
  const [state, dispatch] = useReducer(userReducer, initialState);
  
  // React сравнивает старый и новый объект по ссылке
  // Если вернуть тот же объект — React не заметит изменений
  // Если вернуть новый объект — React заметит изменение
}

2. Работает с DevTools

// Redux DevTools зависит от чистоты reducer'ов
// С мутацией — история действий ломается
// С новым состоянием — можно откатить на любой момент

3. Предсказуемость

// С чистым reducer'ом легко тестировать
const result1 = userReducer(initialState, action);
const result2 = userReducer(initialState, action);
console.log(result1 === result2); // true - одинаковые результаты

Работа с вложенными объектами

Вложенные объекты требуют особого внимания:

// ПЛОХО! Неправильная работа с вложенными объектами
function todoReducer(state, action) {
  const newState = { ...state }; // только поверхностное копирование
  newState.user.name = 'Новое имя'; // мутация вложенного объекта!
  return newState;
}

// ХОРОШО! Копируем все уровни
function todoReducer(state, action) {
  return {
    ...state,
    user: {
      ...state.user,
      name: action.payload
    }
  };
}

// Еще лучше — использовать immer для упрощения
import produce from 'immer';

function todoReducer(state, action) {
  return produce(state, draft => {
    draft.user.name = action.payload; // выглядит как мутация, но это не так
  });
}

Примеры для разных структур данных

Изменение массива

// ПЛОХО
function itemsReducer(state, action) {
  state.items.push(action.payload); // мутация
  return state;
}

// ХОРОШО - создать новый массив
function itemsReducer(state, action) {
  return {
    ...state,
    items: [...state.items, action.payload]
  };
}

// Или использовать методы, которые не мутируют
function itemsReducer(state, action) {
  return {
    ...state,
    items: state.items.concat(action.payload)
  };
}

Удаление элемента из массива

// ПЛОХО
function itemsReducer(state, action) {
  state.items.splice(action.payload, 1); // мутация
  return state;
}

// ХОРОШО
function itemsReducer(state, action) {
  return {
    ...state,
    items: state.items.filter((_, index) => index !== action.payload)
  };
}

Изменение элемента массива

// ПЛОХО
function itemsReducer(state, action) {
  state.items[action.index].completed = true; // мутация
  return state;
}

// ХОРОШО
function itemsReducer(state, action) {
  return {
    ...state,
    items: state.items.map((item, index) =>
      index === action.index
        ? { ...item, completed: true }
        : item
    )
  };
}

Практический пример: useReducer в React

import { useReducer } from 'react';

const initialState = {
  todos: [],
  filter: 'all',
  user: { name: 'Иван' }
};

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
      
    case 'REMOVE_TODO':
      return {
        ...state,
        todos: state.todos.filter(t => t.id !== action.payload)
      };
      
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(t =>
          t.id === action.payload
            ? { ...t, completed: !t.completed }
            : t
        )
      };
      
    case 'SET_FILTER':
      return {
        ...state,
        filter: action.payload
      };
      
    case 'UPDATE_USER':
      return {
        ...state,
        user: {
          ...state.user,
          ...action.payload
        }
      };
      
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  
  const addTodo = (title) => {
    dispatch({
      type: 'ADD_TODO',
      payload: { id: Date.now(), title, completed: false }
    });
  };
  
  return (
    <div>
      {state.todos.map(todo => (
        <div key={todo.id}>
          {todo.title}
          <button onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}>
            {todo.completed ? 'Отметить' : 'Выполнено'}
          </button>
        </div>
      ))}
    </div>
  );
}

Инструменты для упрощения работы с неизменяемостью

1. Immer.js

import produce from 'immer';

const newState = produce(state, draft => {
  draft.user.name = 'Новое имя';
  draft.todos.push({ id: 1, title: 'Task' });
  // Выглядит как мутация, но Immer гарантирует неизменяемость
});

2. Structuralsharing от Redux Toolkit

import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: { name: 'Иван' },
  reducers: {
    setName: (state, action) => {
      state.name = action.payload; // Redux Toolkit использует Immer под капотом
    }
  }
});

Чеклист правильного Reducer'а

  • Не мутирует входное состояние
  • Возвращает новый объект состояния
  • Является pure function (чистой функцией)
  • Всегда возвращает одинаковый результат для одинаковых входных параметров
  • Не имеет побочных эффектов
  • Правильно копирует вложенные объекты и массивы
  • Работает с Redux DevTools
  • Легко тестируется

Неизменяемость в reducer'ах — это не просто рекомендация, это фундаментальный принцип современной разработки на React.

Возвращает ли Reducer новое состояние, или меняет старое | PrepBro