Возвращает ли action копию store в Redux
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Возвращает ли 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
-
Action НЕ должен мутировать state
// ПЛОХО action.state.count += 1; -
Reducer ДОЛЖЕН возвращать новый объект если state изменился
// ХОРОШО return { ...state, count: state.count + 1 }; -
Если state не изменился, return старый state
case 'UNKNOWN_ACTION': return state; // Возвращаем старый state, это OK -
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. Вот что на самом деле происходит:
-
Action — просто объект, который описывает событие
{ type: 'INCREMENT', payload: 1 } -
Action creator — функция, которая возвращает action
function increment(value) { return { type: 'INCREMENT', payload: value }; } -
Reducer — функция, которая принимает (oldState, action) и возвращает НОВЫЙ state
(state, action) => newState // НОВЫЙ объект -
Store — хранилище, которое обновляется после dispatch
store.dispatch(action) // Вызывает reducer, обновляет store
Главное правило: Reducer ДОЛЖЕН создавать копию state (immutable), не мутировать старый state. Если вернёшь тот же объект, Redux/React не заметят изменение.