Hooks дають функціональному компоненту пам’ять і можливість синхронізуватися із зовнішнім світом. На старті достатньо зрозуміти дві речі: useState зберігає стан між рендерами, а useEffect запускає побічну дію після рендеру.
Компонент і повторний рендер
Компонент — функція, яка описує частину інтерфейсу. Коли props або state змінюються, React знову викликає цю функцію. Локальна звичайна змінна не підходить для даних, які повинні пережити рендер і змінити екран.
useState: стан компонента
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>
Натискань: {count}
</button>;
}
count — поточне значення, setCount — функція оновлення. Не змінюйте state напряму. Для об’єкта створюйте нове значення: setUser({...user, name: value}).
Коли потрібне функціональне оновлення
Якщо наступний стан залежить від попереднього, безпечніше написати setCount(current => current + 1). Це особливо важливо для кількох оновлень в одній події.
useEffect: dependencies, запит до API та cleanup
Effect синхронізує component із зовнішньою системою: API, timer, browser event або subscription. Logic, яку природно виконати після натискання кнопки, краще залишити безпосередньо в event handler. Похідне значення, яке можна обчислити під час render, також не потребує effect.
Без dependency array effect запускається після кожного render; [] означає синхронізацію під час mount; [courseId] — повторну синхронізацію після зміни courseId. Це не обіцянка «рівно одного запуску»: у development React Strict Mode може виконати додатковий setup/cleanup cycle, щоб виявити небезпечні effects.
function Course({ courseId }) {
const [course, setCourse] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setError('');
fetch(`/api/courses/${courseId}`, { signal: controller.signal })
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(setCourse)
.catch(error => {
if (error.name !== 'AbortError') setError('Не вдалося завантажити курс');
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [courseId]);
}
Cleanup скасовує попередній request, коли component зникає або courseId змінюється. Dependencies не приховуйте disable-коментарем: перевірте, чи effect справді потрібен і чи всі використані зовнішні значення враховані.
Типові помилки
- Виклик setter під час рендеру.
- Мутація масиву через
push. - Копіювання props у state без причини.
- Запит до API без loading, error і cleanup.
- Effect для простого фільтрування масиву.
Невелика практика
Створіть список нотаток: поле вводу, додавання, видалення і лічильник. Збережіть список у localStorage через effect. Потім поясніть, які дані є state, а які можна обчислити.
Derived values не завжди є state
Якщо filtered list можна отримати з масиву та search query під час рендеру, не зберігайте його окремо. Два пов’язані state легко розсинхронізувати. Залишайте мінімальне джерело правди, а похідні значення обчислюйте.
const visibleTasks = tasks.filter(task =>
task.title.toLowerCase().includes(query.toLowerCase())
);
Checklist review компонента
- State мінімальний і має зрозумілу початкову форму.
- Setter не викликається під час рендеру.
- Lists мають стабільні keys.
- Effect працює із зовнішньою системою.
- Усі dependencies пояснювані.
- Subscription має cleanup.
Коли ці правила зрозумілі, hooks перестають бути набором рецептів і стають передбачуваним способом керувати життєвим циклом компонента.
Наступний крок
Якщо мовна база ще нестійка, почніть зі статті JavaScript після основ: коли переходити до React. Практику компонентів, API та hooks продовжує Frontend Development: JavaScript & React.