Skip to main content
Практика завдань

Незмінність у стані React

Чому незмінність важлива в React?

React покладається на порівняння посилань для виявлення змін стану. Якщо ви безпосередньо змінюєте об'єкт, React не виявить зміну і не перерисує. Ось чому стан завжди повинен оновлюватися незмінно — створюючи новий об'єкт/масив замість модифікації існуючого.


Проблема: Пряма мутація

tsx
const [user, setUser] = useState({ name: "Alice", age: 25 }); // ❌ НЕПРАВИЛЬНО — мутація існуючого об'єкта function updateAge() { user.age = 26; // Те саме посилання! setUser(user); // React бачить те саме посилання → немає перерисовки } // ❌ НЕПРАВИЛЬНО — мутація існуючого масиву const [items, setItems] = useState(["a", "b", "c"]); function addItem() { items.push("d"); // Мутує оригінальний масив setItems(items); // Те саме посилання → немає перерисовки }

Рішення: Незмінні оновлення

Об'єкти

tsx
const [user, setUser] = useState({ name: "Alice", age: 25 }); // ✅ Оператор розподілу — створює новий об'єкт setUser({ ...user, age: 26 }); // ✅ Вкладені об'єкти const [state, setState] = useState({ user: { name: "Alice", address: { city: "Kyiv" } } }); setState({ ...state, user: { ...state.user, address: { ...state.user.address, city: "Lviv" } } });

Масиви

tsx
const [items, setItems] = useState([1, 2, 3]); // ✅ Додати елемент setItems([...items, 4]); // ✅ Видалити елемент setItems(items.filter(item => item !== 2)); // ✅ Оновити елемент setItems(items.map(item => item === 2 ? 20 : item)); // ✅ Вставити за індексом const index = 1; setItems([...items.slice(0, index), 99, ...items.slice(index)]);

Чек-лист незмінних операцій

ОпераціяМутуюча (❌)Незмінна (✅)
Додати до масивуpush(), unshift()[...arr, item], [item, ...arr]
Видалити з масивуsplice(), pop()filter()
Замінити в масивіarr[i] = xmap()
Сортувати масивsort()[...arr].sort()
Оновити об'єктobj.key = val{ ...obj, key: val }
Видалити властивістьdelete obj.keyconst { key, ...rest } = obj

Чому React потребує незмінності

tsx
// React порівнює посилання (Object.is) const prevState = { name: "Alice" }; const nextState = prevState; prevState === nextState // true → React пропускає перерисовку // Новий об'єкт = нове посилання const nextState = { ...prevState, name: "Bob" }; prevState === nextState // false → React перерисовує ✅

Це також пояснює, чому React.memo, useMemo та useCallback працюють — вони порівнюють посилання.

Бібліотеки для складних незмінних оновлень

Immer (найпопулярніша)

tsx
import { produce } from "immer"; const [state, setState] = useState({ users: [ { id: 1, name: "Alice", scores: [90, 85] }, { id: 2, name: "Bob", scores: [70, 75] }, ] }); // ✅ Immer дозволяє вам "мутувати" чернетку — створює незмінний результат setState(produce(draft => { const user = draft.users.find(u => u.id === 1); if (user) { user.name = "Alice Updated"; user.scores.push(95); } }));

useImmerReducer

tsx
import { useImmerReducer } from "use-immer"; const [state, dispatch] = useImmerReducer( (draft, action) => { switch (action.type) { case "addTodo": draft.todos.push(action.payload); // "мутація" безпечна з Immer break; case "toggleTodo": const todo = draft.todos.find(t => t.id === action.id); if (todo) todo.done = !todo.done; break; } }, { todos: [] } );

Важливо:

Ніколи не мутуйте стан React безпосередньо. Завжди створюйте нові об'єкти/масиви, щоб викликати перерисовки. Використовуйте оператор розподілу для простих оновлень і Immer для складного вкладеного стану. Незмінність є основою того, як React виявляє зміни, оптимізує рендеринг і робить стан передбачуваним.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань