useState: синтаксис та ініціалізація.Досі ми розглядали компоненти просто як функції, які отримують дані ззовні (Props) і малюють HTML. Але більшість компонентів у реальних додатках мають бути інтерактивними. Світла чи темна тема, товари в кошику, текст, який користувач зараз вводить у поле пошуку — все це дані, які постійно змінюються з часом.
Якби ми зберігали ці дані у звичайних змінних (let counter = 0), React би просто проігнорував їхні зміни. Інтерфейс (UI) не знав би, що counter став рівним 1, і на екрані залишився б нуль.
Щоб змусити React “перемалювати” компонент при зміні даних, ми повинні використовувати Стан (State).
З 2018 року основним способом управління станом у функціональних компонентах є технологія Hooks (Хуки).
Мета цієї лекції — оволодіти найголовнішим хуком — useState, зрозуміти концепцію незмінності (немутабельності) та навчитися працювати з HTML-формами у стилі React.
Стан (State) — це локальна “Пам’ять” компонента. Це дані, які:
Різниця між Props і State: Props передаються ззовні (як аргументи функції), і їх не можна змінювати. State створюється та зберігається всередині самого компонента, і це єдині дані, які компонент має повне право змінювати самостійно.
Хук (від англ. hook — гачок) — це спеціальна вбудована функція React, яка дозволяє функціональним компонентам “підчепитися” до певних фіч React (наприклад, до управління станом або життєвим циклом), які раніше були доступні тільки класовим компонентам.
Всі хуки традиційно починаються зі слова use.
Два залізні правила हुків (Rules of Hooks):
for, умов if або вкладених функцій. З погляду рушія React, порядок виклику хуків ніколи не повинен змінюватися від рендеру до рендеру.useStateuseState — це фундаментальний хук. Він використовується для оголошення змінної стану.
Щоб його використати, спочатку його треба імпортувати:
// Імпортуємо вгорі файлу
import { useState } from "react";
Синтаксис (useState завжди використовує ручну деструктуризацію масиву):
// Початкове значення - 0
const [count, setCount] = useState(0);
Розберемо цей рядок:
useState(0) — ми кажемо: “Дай мені стан, який зі старту дорівнює 0”.useState повертає масив з рівно двома елементами.count (перший елемент) — це константа, в якій лежить ПОТОЧНЕ значення (зараз 0).setCount (другий елемент) — це спеціальна ФУНКЦІЯ (сеттер). ЄДИНИЙ законний спосіб змінити count — це викликати setCount(нове_значення).Приклад (Лічильник натискань):
import { useState } from "react";
function CounterButton() {
const [clicks, setClicks] = useState(0);
const handleClick = () => {
// Кажемо React: зміни clicks на (clicks + 1)
// React побачить зміну і перемалює цей компонент автоматично
setClicks(clicks + 1);
};
return <button onClick={handleClick}>Ти натиснув мене {clicks} разів</button>;
}
Якщо ви спробуєте написати clicks = clicks + 1; — змінна в пам’яті оновиться, але на екрані НІЧОГО не зміниться, бо React не дізнається про цю махінацію без виклику setClicks().
Одна з найпоширеніших пасток для новачків — нерозуміння того, як саме працюють сеттери (наприклад, setClicks).
const handleClick = () => {
setClicks(clicks + 1);
// Що виведе консоль одразу після зміни?
console.log(clicks);
};
Консоль виведе СТАРЕ значення!
Функції зміни стану в React (сеттери) працюють асинхронно. Вони лише створюють “заявку” на оновлення компонента, але не зупиняють виконання поточного коду і не змінюють константу clicks миттєво. Резервування нового значення відбудеться тільки в наступному рендері компонента.
Якщо вам потрібно двічі підряд оновити стан в одному кліку на основі попереднього, потрібно передавати у сеттер не значення, а callback-функцію:
const handleDoubleIncrease = () => {
// React гарантує, що в prev (previous) буде найсвіжіше значення з черги
setClicks((prev) => prev + 1);
setClicks((prev) => prev + 1);
};
Коли ви зберігаєте об’єкти чи масиви у стані, ви повинні пам’ятати про концепт Немутабельності (Immutability) — тобто незмінності.
В React суворо заборонено змінювати (мутувати) старий об’єкт або масив стану напряму.
userState.age = 25; setUserState(userState);ordersList.push(newOrder); setOrders(ordersList);Чому? Тому що React використовує не глибоке порівняння, а порівняння за “посиланням у пам’яті” (Reference equality). Якщо ви змінили стару коробочку ordersList.push і передали її ж у setOrders, React подумає: “Ага, посилання на об’єкт те ж саме, отже нічого не змінилося. Я не буду перемальовувати сайт”.
Правило: Завжди СТВОРЮЙТЕ НОВИЙ об’єкт чи масив (копію старого), змінюйте копію і передавайте її в сеттер!
Найпростіше це робити за допомогою spread оператора ... (Трьох крапок).
Робота з Масивом:
const [todos, setTodos] = useState(["Купити хліб"]);
const addTodo = () => {
// Створюємо НОВИЙ масив [], кладемо туди все зі старого (...todos), і додаємо нове.
setTodos([...todos, "Зробити домашку"]);
};
Робота з Об’єктом:
const [user, setUser] = useState({ name: "Іван", age: 20 });
const updateAge = () => {
// Створюємо НОВИЙ об'єкт {}, копіюємо всі поля (...user), а поле 'age' - ПЕРЕЗАПИСУЄМО
setUser({ ...user, age: 21 });
};
У звичайному HTML, <input> сам тримає власний текст всередині себе. У React всі дані мають бути централізовані в React State. Це створює концепцію Керованого Компонента (Controlled Component).
Щоб поле стало керованим:
value жорстко прив’язується до змінної зі стану.onChange викликає сеттер і оновлює стан.function RegistrationForm() {
// Стан для інпуту
const [login, setLogin] = useState("");
const handleChange = (e) => {
// e.target.value — це те, що користувач щойно надрукував на клавіатурі
setLogin(e.target.value);
};
return (
<form>
<label>Ваш логін: </label>
<input type="text" value={login} onChange={handleChange} />
{/* Текст параграфа миттєво змінюватиметься з кожним натисканням клавіші */}
<p>Довжина вашого логіна: {login.length} символів.</p>
</form>
);
}
Якщо ви вкажете <input value={login} />, але забудете дописати onChange, поле стане заблокованим. Користувач не зможе нічого туди надрукувати, бо React жорстко триматиме значення login (яке дорівнює порожньому рядку).
Де має жити (оголошуватися) стан?
State завжди належить лише одному конкретному компоненту (де був викликаний useState). Пропси можуть їхати ТІЛЬКИ ВНИЗ, до дітей.
Але що робити, якщо два різні компоненти (наприклад, <Sidebar> і <MainContent>) на одному рівні потребують доступу до однієї і тієї ж інформації (наприклад, відкрито чи закрито меню)?
Рішення: Ми переносимо (піднімаємо) цей загальний стан вгору — у їхнього найближчого спільного компонента-Батька (наприклад, в App.jsx).
Батько зберігає стан isOpen і відправляє його значення в <MainContent> через Props.
Як дозволити <Sidebar> закрити меню? Батько передає в <Sidebar> через Props не тільки значення, але й функцію-сеттер!
function App() {
const [isOpen, setIsOpen] = useState(false);
// Батько спускає дітям пропси та функції
return (
<div>
{/* Меню отримує функцію для зміни стану свого батька! */}
<Menu isOpen={isOpen} closeMenu={() => setIsOpen(false)} />
<MainContent showingPanel={isOpen} />
</div>
);
}
hooks) — це функції (на кшталт useState), що дозволяють підключатися до екосистеми React. Вони повинні викликатися виключно на верхньому рівні функціонального компонента.useState повертає масив із поточної константи-значення та функції для її зміни (яка працює асинхронно)....), змінювати їх, і передавати у State Setter.onChange, а атрибут value прив’язано до цього стану.... оператор.State фундаментально відрізняється від Props з точки зору права на зміну даних?count = count + 1), не використовуючи функцію setCount()?use) заборонено ставити всередину умовних конструкцій if () {...}?setX(...)?push() для масивів, які зберігаються у стейті? Як правильно додати елемент?<input />? Які два атрибути (пропси) обов’язкові для його правильної роботи?useState?