Пошук уроків, статей та іншого контенту
Синхронізація компонента із зовнішнім світом: запити, підписки, таймери — і масив залежностей.
Тіло функції-компонента має лише обчислювати й повертати JSX на основі props і стану — без звернень до мережі, роботи з таймерами чи прямої підписки на події поза цим обчисленням. Усе, що «виходить за межі» простого обчислення UI (запит до сервера, підписка на подію, ручна робота з DOM, таймер), називається побічним ефектом, і для нього існує окремий хук — useEffect.
useEffect(() => {
console.log("Ефект виконано після рендеру");
}, [/* масив залежностей */]);Масив залежностей визначає, коли ефект повторно запускається:
Без масиву взагалі — ефект запускається після кожного рендеру компонента (рідко потрібно, легко створити зайве навантаження).
Порожній масив [] — ефект запускається лише один раз, після першого рендеру (типово для одноразового завантаження даних).
Масив зі значеннями [userId] — ефект запускається після першого рендеру й після кожної зміни будь-якого значення в масиві.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then(setUser);
}, [userId]); // повторний запит при зміні userId
if (!user) return <Spinner />;
return <UserCard user={user} />;
}Якщо ефект підписується на щось (подію, таймер, зовнішнє джерело даних), він повинен повернути функцію очищення — React викликає її перед повторним запуском ефекту й перед розмонтуванням компонента, щоб прибрати попередню підписку і не накопичувати витоки:
useEffect(() => {
const id = setInterval(() => {
console.log("тік");
}, 1000);
return () => clearInterval(id); // прибирає таймер
}, []);У розробницькій збірці React 18 навмисно запускає кожен ефект двічі (монтування → розмонтування → монтування) під React.StrictMode, щоб стрес-тестувати саме функцію очищення — детально розібрано в статті «Чому useEffect виконується двічі в React». Це не відбувається в продакшн-збірці.
Якщо ефект використовує змінну з props/стану, але вона не вказана в масиві залежностей, ефект «бачитиме» застаріле значення цієї змінної із замикання, зафіксованого на момент першого запуску — так само, як класична пастка var у циклі з setTimeout з основного курсу JavaScript:
function SearchResults({ query }) {
useEffect(() => {
fetch(`/api/search?q=${query}`); // query тут завжди буде початковим значенням!
}, []); // забутий query в масиві залежностей
// Правильно:
useEffect(() => {
fetch(`/api/search?q=${query}`);
}, [query]);
}Неповний масив залежностей — ефект використовує змінну, якої немає в масиві, і працює зі застарілим значенням із першого рендеру.
Оновлення стану всередині ефекту без масиву залежностей узагалі (або з масивом, що включає саме це значення стану без потреби) — кожен рендер запускає ефект, ефект оновлює стан, стан викликає новий рендер — нескінченний цикл.
Відсутня функція очищення для підписки/таймера/слухача подій — після розмонтування компонента підписка продовжує жити й може викликати оновлення стану вже неіснуючого компонента.
useEffect синхронізує компонент із зовнішнім світом — мережею, таймерами, підписками — тобто з усім, що виходить за межі простого обчислення JSX з props і стану. Масив залежностей визначає, коли ефект перезапускається, і має включати кожну зовнішню змінну, яку ефект реально використовує. Ефекти з підпискою чи таймером повинні повертати функцію очищення, яку React викликає перед повторним запуском і при розмонтуванні компонента.