Новий об'єкт під час оновлення стану
Коротка відповідь
Тому що React не відстежує внутрішні зміни об'єктів, а визначає, чи змінився стан, за посиланням на об'єкт.
Якщо ти мутуєш (змінюєш) старий об'єкт напряму, React думає, що стан не змінився, і не перемальовує компонент.
1. Що відбувається, коли ти робиш так:
const [user, setUser] = useState({ name: "Alex", age: 25 });
function updateName() {
user.name = "Tim"; // mutating the object directly
setUser(user);
}Що бачить React:
- Старе посилання: 0xA12F
- Нове посилання: 0xA12F (те саме, адже об'єкт не створювався заново)
- React порівнює посилання і вирішує: "Об'єкт той самий, отже, змін немає."
Результат: компонент не перерендериться, UI залишиться старим, попри зміну даних.
2. Правильний спосіб - створити новий об'єкт
setUser({ ...user, name: "Tim" });Що тепер відбувається:
- Старий об'єкт -> 0xA12F
- Новий об'єкт -> 0xB83C (у нього та сама структура, але інше посилання)
- React порівнює посилання і розуміє: "Це новий об'єкт, потрібно оновити компонент."
Компонент ререндериться, UI оновлюється.
3. Чому React порівнює посилання, а не значення
React використовує поверхневе (shallow) порівняння, щоб визначити, чи потрібно оновлювати компонент:
oldState === newStateВін не робить глибокого порівняння (deep equal), інакше це було б надто повільно для великих об'єктів.
Тому React визначає зміну стану за зміною посилання (вказівника). Якщо посилання на об'єкт змінилося, стан новий, оновлюємо UI.
4. Приклад із масивом
Той самий принцип для масивів:
Погано:
items.push("new"); // mutating the array
setItems(items);Добре:
setItems([...items, "new"]); // creating a new arrayReact бачить нове посилання на масив і оновлює компонент.
5. Чому мутація - це ще й логічна помилка
Якщо ти мутуєш старий стан, то:
- старі посилання (у замиканнях, хуках, props, ефектах) тепер вказують на змінені дані - це ламає передбачуваність,
- React може «думати», що стан старий, і не викликати потрібні ефекти,
- devtools і time-travel debugging стають неможливими (не можна «відкотити» стан назад, якщо він мутований).
6. Приклад наочно
function UserCard() {
const [user, setUser] = useState({ name: "Alex", age: 25 });
const handleClick = () => {
// Direct mutation
user.age++;
setUser(user); // won't work
};
const handleClickCorrect = () => {
// Immutable update
setUser(prev => ({ ...prev, age: prev.age + 1 }));
};
console.log("Re-render:", user);
return (
<>
<p>{user.name} - {user.age}</p>
<button onClick={handleClick}>Bad</button>
<button onClick={handleClickCorrect}>Good</button>
</>
);
}При натисканні на «Bad» React не перерендериться, а при «Good» - оновить UI як потрібно.
Підсумок
| Поведінка | Пряма мутація | Копіювання ({ ...obj }) |
|---|---|---|
| Змінюється посилання | Ні | Так |
| React бачить зміну | Ні | Так |
| Відбувається ререндер | Ні | Так |
| Передбачуваність | Ламається | Зберігається |
| Підтримка DevTools / time travel | Неможлива | Можлива |
Підсумкова думка:
У React стан має бути іммутабельним (незмінюваним). Завжди створюй новий об'єкт або масив замість зміни старого - тоді React зможе коректно визначити, що стан змінився, і оновити інтерфейс.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.