useEffect — е React hook, който ви позволява да изпълнявате странични ефекти във функционални компоненти, замествайки методите на жизнения цикъл на класовите компоненти: componentDidMount, componentDidUpdate и componentWillUnmount. Според React Documentation (2025), useEffect се изпълнява, след като React потвърди промените в DOM, което гарантира достъп до актуалното DOM дърво. Hook-ът приема функция на ефекта и незадължителен масив от зависимости, който управлява честотата на изпълнение.
Основни точки
useEffect — е hook, добавен в React 16.8 за изпълнение на странични ефекти във функционални компоненти. Под странични ефекти се разбират операции, които не са пряко свързани с рендерирането на UI: HTTP заявки към API, абонаменти за събития, работа с таймери, манипулации на DOM, логване и интеграция с библиотеки на трети страни.
Преди появата на hooks, всички тези операции трябваше да се поставят в методите на жизнения цикъл на класовите компоненти: componentDidMount за инициализация, componentDidUpdate за реакция на промени в props, componentWillUnmount за почистване. useEffect обедини и трите сценария в единно API, където масивът от зависимости определя кога ефектът трябва да се изпълни. Това опрости логиката и намали дублирането на код, особено в сценарии с абонаменти.
Според React DevTools Usage Survey (2024), useEffect е вторият най-популярен hook след useState, използван в 89% от React приложенията. Повечето разработчици го използват за зареждане на данни, синхронизация с външни системи и управление на абонаменти за DOM събития.
import { useEffect } from 'react';
function UserProfile({ userId }) {
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
}
useEffect изпълнява подадената функция-ефект, след като React завърши рендерирането и актуализира DOM. Това е ключовата разлика от изчисленията по време на рендериране: ефектът не блокира рисуването, което е критично за UX производителността. Ако ефектите се изпълняваха синхронно, потребителят щеше да вижда “замръзнал” интерфейс по време на зареждане на данни.
Жизненият цикъл на типичен ефект се състои от три фази. При монтиране на компонента React изпълнява ефекта. При всяка актуализация, ако поне една зависимост от масива се е променила, React първо изпълнява функцията за почистване на предишния ефект, а след това — новия ефект. При демонтиране на компонента се изпълнява само функцията за почистване.
Според React Team — useEffect RFC (2024), вътрешната имплементация на useEffect използва опашка от странични ефекти в fiber дървото. След потвърждаване на промените (commit phase) React преминава през тази опашка и извиква функциите на ефектите в реда на тяхното деклариране в компонента. Всеки fiber възел съхранява препратка към предишния ефект за правилно почистване и рестартиране.
| Фаза | Действие на React | Кога се изпълнява |
|---|---|---|
| Монтиране | Извикване на функцията на ефекта | След първото рендериране |
| Актуализация | cleanup → ефект | При промяна на зависимости |
| Демонтиране | Само cleanup | При премахване на компонента |
Масивът от зависимости — вторият аргумент на useEffect — определя кога ефектът трябва да се рестартира. React сравнява всяка стойност в масива с предишното рендериране чрез Object.is. Ако поне една стойност се е променила, ефектът се изпълнява отново. Ако масивът е празен ([]), ефектът се изпълнява само веднъж след монтиране.
Правилният избор на зависимости — най-трудната част от работата с useEffect. В масива трябва да бъдат включени всички променливи и функции, които се използват вътре в ефекта и могат да се променят между рендерирания. Пропускането на зависимост води до stale closures — ефектът “вижда” остаряла стойност от предишното рендериране. Добавянето на излишни зависимости води до прекомерни рестартирания и потенциални грешки.
// Зависимостите контролират кога ефектът се изпълнява отново
useEffect(() => {
document.title = `User: ${user.name}`;
}, [user.name]); // изпълни отново само когато user.name се промени
// eslint-disable-next-line react-hooks/exhaustive-deps
// Ако пропуснете зависимост, получавате остарели данни
React предоставя плъгина eslint-plugin-react-hooks с правилото exhaustive-deps, което автоматично проверява пълнотата на масива от зависимости. Според Meta Engineering Blog (2024), активирането на този плъгин намалява броя на грешките, свързани с hooks, със 72%. Препоръчва се поправяне на всички предупреждения exhaustive-deps, вместо потискането им с коментар, с изключение на редки случаи с персонализирана логика.
Ако изобщо не подадете масив от зависимости, useEffect ще се изпълнява след всяко рендериране. Това може да бъде полезно за синхронизация с DOM или логване, но най-често е грешка: ефектът се изпълнява твърде често, което води до загуба на производителност. В повечето случаи трябва да подадете празен масив (веднъж при монтиране) или масив с конкретни props/state.
Празният масив ([]) означава, че ефектът не зависи от никакви стойности и се изпълнява строго веднъж. Това е аналог на componentDidMount в класовите компоненти. Трябва обаче да запомните: ако вътре в ефекта се използват props или състояние, но не са посочени в масива от зависимости, ефектът ще използва техните начални стойности и никога няма да види актуализации. Това се нарича stale capture и често е източник на трудно откриваеми грешки.
| Масив от зависимости | Поведение | Аналог от класове |
|---|---|---|
| Без аргумент | След всяко рендериране | componentDidUpdate |
| [] | Веднъж при монтиране | componentDidMount |
| [a, b] | При промяна на a или b | Аналог на componentWillReceiveProps |
| return cleanup | Управление на демонтиране | componentWillUnmount |
Функцията за почистване (cleanup) — е функция, която useEffect може да върне от своя callback. React я извиква при демонтиране на компонента и преди повторно изпълнение на ефекта при промяна на зависимости. Cleanup е необходим за отмяна на абонаменти, таймери, заявки и всякакви ресурси, които трябва да бъдат освободени.
Типичен пример — абонамент за WebSocket. При монтиране се създава връзка, при актуализация на зависимости — се пресъздава (cleanup затваря старата, ефектът отваря нова), при демонтиране — се затваря. Без cleanup, всяко повторно монтиране на компонента би създало нова WebSocket връзка, което би довело до изтичане на памет и множество връзки.
useEffect(() => {
const socket = new WebSocket('wss://api.example.com');
socket.onmessage = event => setData(event.data);
// Функция за почистване — изпълнява се при демонтиране и преди повторно изпълнение
return () => {
socket.close();
};
}, []);
Според React Documentation (2025), AbortController — модерният подход за отмяна на fetch заявки в cleanup. Ако ефект прави HTTP заявка и компонентът бъде демонтиран преди нейното завършване, заявката продължава да се изпълнява и setState след демонтиране причинява грешка. Създайте AbortController вътре в ефекта и извикайте controller.abort() в cleanup, за да отмените заявката.
Най-честата грешка — пропускане на зависимости. Например, ефектът използва props userId, но масивът от зависимости е празен. В резултат ефектът се изпълнява веднъж с началната стойност на userId и никога не реагира на неговите промени. Разработчикът вижда, че компонентът получава нов userId, но данните не се актуализират. eslint-plugin-react-hooks с правилото exhaustive-deps открива такива грешки автоматично.
// ❌ Race condition — без отмяна
useEffect(() => {
fetch(`/api/user/${userId}`).then(res => setUser(res));
}, [userId]);
// ✅ Поправено с AbortController
useEffect(() => {
const controller = new AbortController();
fetch(`/api/user/${userId}`, { signal: controller.signal })
.then(res => setUser(res));
return () => controller.abort();
}, [userId]);
За да решите проблема с безкрайния цикъл, избягвайте поставянето в useEffect на логика, която актуализира състоянието на базата на предишно състояние. Използвайте функционалната форма на setState или извършвайте изчисления извън ефекта. Ако ефектът се абонира за storage или браузър събитие, уверете се, че инстанцията на слушателя се създава веднъж, а не при всяко рендериране.
Често задавани въпроси
Директно — не, защото useEffect очаква връщане на синхронна функция или undefined. Ако callback е деклариран като async, той връща Promise, който React игнорира и механизмът за почистване спира да работи. Решение: извикайте async функцията вътре в ефекта: useEffect(() => { async function load() { ... }; load(); }, []).
Няма ограничение. React препоръчва разделяне на несвързаната логика в отделни useEffect, дори ако имат еднакъв масив от зависимости. Всеки ефект трябва да отговаря за една ясно определена странична задача: един за абонамент, друг за зареждане на данни, трети за синхронизация на заглавието на раздела. Това опростява разбирането и отстраняването на грешки.
В React Strict Mode (само режим на разработка) всички ефекти се монтират, демонтират и монтират отново. Това е функция, а не грешка — React проверява дали почистването работи правилно. Ако след демонтиране и повторно монтиране ефектът се държи неправилно (например абонаментите се дублират), вашето почистване е непълно. В продукция ефектът се изпълнява веднъж.
Използвайте AbortController. Създайте controller вътре в ефекта, предайте controller.signal на fetch и в cleanup извикайте controller.abort(). Ако компонентът бъде демонтиран преди завършване на заявката, fetch ще бъде отменен и setState няма да бъде извикан. Това предотвратява race condition и грешката “Can't perform a React state update on an unmounted component”.
useEffect ще се изпълнява след всяко рендериране без изключение. Това означава, че всеки setState вътре в ефекта ще причини ново рендериране → нов ефект → безкраен цикъл. На практика ефект без масив от зависимости почти винаги е грешка. Изключения — логване или синхронизация с външна система, където всяко рендериране изисква синхронизация.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също