← Назад к вопросам
Возвращает ли 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.