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

Возвращает ли action копию store в Redux

1.8 Middle🔥 221 комментариев
#State Management

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

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

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

Возвращает ли action копию store в Redux

Это вопрос про архитектуру Redux и то, как работает state management. Ответ не простой, потому что нужно понять разницу между action, reducer и store. Расскажу подробно.

1. Redux архитектура

User Action (click)
      |
      v
Dispatch Action { type: 'INCREMENT', payload: 1 }
      |
      v
Reducer (oldState + action) => newState
      |
      v
Store обновляет state
      |
      v
Subscribed components получают уведомление
      |
      v
Re-render

2. Что НЕ возвращает action

Action НЕ возвращает копию store. Action — это просто объект:

const action = {
  type: 'INCREMENT',
  payload: 1
};

// action НЕ есть функция, которая возвращает что-то
// action — это объект, который ОПИСЫВАЕТ что произошло

Функция, которая создаёт action, называется action creator:

// Action creator
function increment(value) {
  return {
    type: 'INCREMENT',
    payload: value
  };
}

// Когда я вызываю action creator, он возвращает action object
const action = increment(1);
console.log(action); // { type: 'INCREMENT', payload: 1 }

Action creator НЕ возвращает copy store, он возвращает action object.

3. Где создаётся копия state

Копия state создаётся в REDUCER, не в action:

// Reducer
function counterReducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      // ЗДЕСЬ создаётся новая копия state
      return state + action.payload; // Новое значение
    
    case 'DECREMENT':
      return state - action.payload;
    
    default:
      return state; // Возвращаем текущий state без изменений
  }
}

// Использование
const oldState = 5;
const action = { type: 'INCREMENT', payload: 1 };
const newState = counterReducer(oldState, action);
console.log(oldState); // 5 (не изменился)
console.log(newState); // 6 (новое значение)

4. Мутация vs Immutability

ПЛОХО: мутация state (нарушает Redux принцип)

function badReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      state.count += action.payload; // МУТАЦИЯ!
      return state; // Возвращаем ТОТ ЖЕ объект
  }
}

const oldState = { count: 5 };
const action = { type: 'INCREMENT', payload: 1 };
const newState = badReducer(oldState, action);

console.log(oldState === newState); // true (один и тот же объект!)
console.log(oldState.count); // 6 (был изменён!)

ПРОБЛЕМА: Redux полагается на то, что если state изменился, то это НОВЫЙ объект (по ссылке). Если вернуть тот же объект, React и Redux не заметят изменение.

ХОРОШО: создание новой копии (immutable)

function goodReducer(state = { count: 0 }, action) {
  switch (action.type) {
    case 'INCREMENT':
      // Создаём НОВЫЙ объект
      return { ...state, count: state.count + action.payload };
    
    case 'DECREMENT':
      return { ...state, count: state.count - action.payload };
    
    default:
      return state;
  }
}

const oldState = { count: 5 };
const action = { type: 'INCREMENT', payload: 1 };
const newState = goodReducer(oldState, action);

console.log(oldState === newState); // false (разные объекты)
console.log(oldState.count); // 5 (не изменился)
console.log(newState.count); // 6 (новое значение)

5. Spread оператор для копирования

const state = {
  user: { name: 'Alice', age: 30 },
  posts: [1, 2, 3]
};

const action = { type: 'UPDATE_NAME', payload: 'Bob' };

// Способ 1: Shallow copy (только верхний уровень)
const newState1 = {
  ...state,
  user: { ...state.user, name: action.payload }
};

// Способ 2: Используя Object.assign
const newState2 = Object.assign({}, state, {
  user: Object.assign({}, state.user, { name: action.payload })
});

// Способ 3: Lodash
import { cloneDeep } from 'lodash';
const newState3 = cloneDeep(state);
newState3.user.name = action.payload;

Важно: Spread оператор делает SHALLOW copy, не DEEP copy. Вложенные объекты всё ещё ссылаются на старые объекты.

6. Практический пример: Redux с объектом state

// Initial state
const initialState = {
  user: null,
  posts: [],
  loading: false,
  error: null
};

// Reducer
function appReducer(state = initialState, action) {
  switch (action.type) {
    case 'SET_USER':
      // Создаём НОВЫЙ state
      return {
        ...state,
        user: action.payload // Новый user объект
      };
    
    case 'ADD_POST':
      // Создаём НОВЫЙ массив posts
      return {
        ...state,
        posts: [...state.posts, action.payload]
      };
    
    case 'SET_LOADING':
      return {
        ...state,
        loading: action.payload
      };
    
    case 'SET_ERROR':
      return {
        ...state,
        error: action.payload
      };
    
    default:
      return state;
  }
}

// Action creators
function setUser(user) {
  return {
    type: 'SET_USER',
    payload: user
  };
}

function addPost(post) {
  return {
    type: 'ADD_POST',
    payload: post
  };
}

// Использование
const oldState = {
  user: null,
  posts: [],
  loading: false,
  error: null
};

const action1 = setUser({ id: 1, name: 'Alice' });
const newState1 = appReducer(oldState, action1);

console.log(oldState === newState1); // false (новый объект)
console.log(oldState.user === newState1.user); // false (новый user)

const action2 = addPost({ id: 1, text: 'Hello' });
const newState2 = appReducer(newState1, action2);

console.log(newState1.posts === newState2.posts); // false (новый array)

7. Store и dispatch

Store НЕ возвращает копию при dispatch:

const store = createStore(appReducer);

// dispatch НЕ возвращает ничего (или возвращает action)
const result = store.dispatch({ type: 'INCREMENT' });
console.log(result); // { type: 'INCREMENT' } (сам action, не новый state)

// Чтобы получить текущий state, используй getState()
const currentState = store.getState();
console.log(currentState); // Текущий state

8. Immer для упрощения immutability

Immer позволяет писать "мутирующий" код, но он создаёт копию:

import { createSlice } from '@reduxjs/toolkit'; // Использует Immer

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state, action) => {
      // Пишем как будто мутируем, но Immer создаёт копию
      state.value += action.payload; // Выглядит как мутация
      // Но на самом деле Immer отслеживает и создаёт новый state
    },
    decrement: (state, action) => {
      state.value -= action.payload;
    }
  }
});

// Результирующий reducer
const reducer = counterSlice.reducer;
const { increment, decrement } = counterSlice.actions;

// Использование
const oldState = { value: 5 };
const newState = reducer(oldState, increment(1));

console.log(oldState === newState); // false (Immer создал копию)
console.log(oldState.value); // 5 (не изменился)
console.log(newState.value); // 6

9. React-Redux и selectors

import { useSelector, useDispatch } from 'react-redux';

function MyComponent() {
  // useSelector НЕ возвращает копию, а возвращает часть state
  const user = useSelector(state => state.user);
  const dispatch = useDispatch();
  
  const handleClick = () => {
    // dispatch отправляет action
    // reducer создаёт новый state
    // useSelector вызовется снова и вернёт новую часть state
    dispatch({ type: 'SET_USER', payload: { name: 'Bob' } });
  };
  
  return <div>{user?.name}</div>;
}

10. Важные правила Redux

  1. Action НЕ должен мутировать state

    // ПЛОХО
    action.state.count += 1;
    
  2. Reducer ДОЛЖЕН возвращать новый объект если state изменился

    // ХОРОШО
    return { ...state, count: state.count + 1 };
    
  3. Если state не изменился, return старый state

    case 'UNKNOWN_ACTION':
      return state; // Возвращаем старый state, это OK
    
  4. Reducer должен быть pure function

    • Одинаковый input => одинаковый output
    • Без side effects (fetch, API calls)
    • Без мутации аргументов

11. Практический пример: правильный vs неправильный

// НЕПРАВИЛЬНО (мутация)
function wrongReducer(state = { items: [] }, action) {
  if (action.type === 'ADD_ITEM') {
    state.items.push(action.payload); // МУТАЦИЯ!
    return state; // Возвращаем тот же объект
  }
  return state;
}

// ПРАВИЛЬНО (immutable)
function rightReducer(state = { items: [] }, action) {
  if (action.type === 'ADD_ITEM') {
    return {
      ...state,
      items: [...state.items, action.payload] // Новый array
    };
  }
  return state;
}

Ключевой вывод

Action НЕ возвращает копию store. Вот что на самом деле происходит:

  1. Action — просто объект, который описывает событие

    { type: 'INCREMENT', payload: 1 }
    
  2. Action creator — функция, которая возвращает action

    function increment(value) {
      return { type: 'INCREMENT', payload: value };
    }
    
  3. Reducer — функция, которая принимает (oldState, action) и возвращает НОВЫЙ state

    (state, action) => newState // НОВЫЙ объект
    
  4. Store — хранилище, которое обновляется после dispatch

    store.dispatch(action) // Вызывает reducer, обновляет store
    

Главное правило: Reducer ДОЛЖЕН создавать копию state (immutable), не мутировать старый state. Если вернёшь тот же объект, Redux/React не заметят изменение.