Пошук уроків, статей та іншого контенту
Що таке стан компонента, як його оновлювати правильно та чому мутація не працює.
Props — дані, отримані ззовні, від батьківського компонента; стан (state) — дані, якими компонент керує сам, і які можуть змінюватись у часі внаслідок дій користувача чи інших подій (введений текст, чи відкрите меню, поточна вкладка). Коли стан компонента змінюється, React автоматично перерендерює цей компонент (і його дочірні), щоб UI відповідав новим даним.
useState — хук, що додає функціональному компоненту власний стан. Він повертає масив із двох елементів: поточне значення й функцію для його оновлення (за конвенцією деструктуризують через масив, а не об'єкт, саме тому, що можна вільно давати власні імена):
function Counter() {
const [count, setCount] = useState(0); // 0 — початкове значення
return (
<div>
<p>Значення: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}Виклик setCount не змінює count негайно й на місці — він повідомляє React: «заплануй перерендер цього компонента з новим значенням». Саме тому в тілі функції-компонента після виклику setCount(count + 1) змінна count усе ще міститиме старе значення до наступного рендеру.
React може об'єднувати (batch) кілька оновлень стану в один рендер для продуктивності, тому пряме використання поточного значення змінної кілька разів поспіль не підсумовується так, як очікується:
function handleTripleClick() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// count у замиканні той самий на всі три виклики — результат +1, а не +3
}
// Виправлення — функція-оновлювач отримує актуальне попереднє значення:
function handleTripleClick() {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
// тепер результат дійсно +3
}React визначає, чи змінився стан, порівнюючи посилання (для об'єктів і масивів) — тому стан ніколи не можна мутувати напряму, потрібно завжди створювати новий об'єкт/масив, як і в незмінних патернах оновлення з основного курсу JavaScript:
const [user, setUser] = useState({ name: "Оксана", age: 28 });
// Неправильно — мутація, React не побачить зміни (те саме посилання)
user.age = 29;
setUser(user);
// Правильно — новий об'єкт зі spread
setUser({ ...user, age: 29 });
const [items, setItems] = useState([1, 2, 3]);
// Неправильно
items.push(4);
setItems(items);
// Правильно
setItems([...items, 4]);Це та сама «поверхнева копія» пастка, що й у звичайному JavaScript: { ...user, address: user.address } копіює лише перший рівень — вкладений об'єкт address і далі спільний з оригіналом, тому мутація newUser.address.city = ... однаково зачепить старий стан.
Мутація стану напряму (push, змінення поля об'єкта) замість створення нового об'єкта/масиву — React не перерендерить компонент, бо посилання на стан не змінилось.
Читання значення стану одразу після виклику функції-оновлювача й очікування нового значення в тому самому рендері — оновлення застосовується асинхронно, до наступного рендеру.
Кілька послідовних setState(value) на основі поточного значення замість функціонального оновлення setState(prev => ...) — призводить до втрачених оновлень через застаріле значення в замиканні.
useState додає компоненту власний, змінюваний у часі стан, на відміну від props, отриманих ззовні. Виклик функції-оновлювача не змінює значення негайно — він планує перерендер з новим значенням. Коли новий стан обчислюється на основі попереднього, безпечніше використовувати функціональне оновлення (prev => ...), а об'єкти й масиви в стані завжди оновлюють створенням нової копії, ніколи мутацією.