
ببین وقتی توی React از useEffect استفاده میکنیم، معمولاً داریم یه کار جانبی انجام میدیم. مثلا:
تایمر میذاریم
به یه event گوش میدیم
درخواست API میزنیم
WebSocket باز میکنیم
subscription میسازیم
حالا نکته مهم چیه؟
اینه که وقتی کامپوننت از بین میره (Unmount میشه) یا دوباره قراره effect اجرا بشه، باید اون چیزایی که ساختیم رو پاک کنیم.
اگه پاکشون نکنیم چی میشه؟
اپ کند میشه
memory leak میخوریم
ارورهای عجیب میاد
state روی کامپوننتی که دیگه وجود نداره ست میشه
ساختار useEffect اینجوریه:
useEffect(() => { // کار اصلی اینجاست return () => { // این قسمت همون cleanup هست }; }, []);
اون تابعی که داخل return برمیگردونی، همون تمیزکاریه. React هر وقت بخواد effect رو ببنده یا دوباره اجرا کنه، اول cleanup قبلی رو صدا میزنه.
useEffect(() => { const interval = setInterval(() => { console.log("hello"); }, 2000); return () => clearInterval(interval); }, []);
اگه clearInterval نزنی، حتی بعد از اینکه از صفحه رفتی بیرون، تایمر همچنان اجرا میشه.
بعد میگی چرا لاگ هنوز میاد؟ چرا باتری خالی میشه؟ چرا اپ کند شده؟
هر setInterval یا setTimeout = حتماً clear کن
توی React خیلی وقتا به event ها گوش میدیم. مثلا کیبورد:
useEffect(() => { const showSub = Keyboard.addListener("keyboardDidShow", () => {}); const hideSub = Keyboard.addListener("keyboardDidHide", () => {}); return () => { showSub.remove(); hideSub.remove(); }; }, []);
اگه remove نکنی، بعداً هم اجرا میشه حتی وقتی اون صفحه دیگه وجود نداره. این باعث رفتارهای غیرقابل پیشبینی میشه. هر addListener = یه remove لازم داره.
فرض کن اینو نوشتی:
useEffect(() => { fetch("https://example.com") .then(res => res.json()) .then(data => setData(data)); }, []);
حالا کاربر سریع از صفحه میره بیرون ولی درخواست هنوز در حال انجامه. وقتی جواب بیاد، میخواد setData بزنه روی کامپوننتی که دیگه وجود نداره.
اینجاست که ارور معروف میاد:
Can't perform a React state update on an unmounted component
راه حل چیه؟ AbortController
useEffect(() => { const controller = new AbortController(); fetch("https://example.com", { signal: controller.signal }) .then(res => res.json()) .then(data => setData(data)) .catch(err => { if (err.name !== "AbortError") { console.error(err); } }); return () => controller.abort(); }, []);
اینجوری وقتی صفحه بسته بشه، درخواست هم لغو میشه.
اگه WebSocket باز کنی و نبندیش، اتصال همچنان باز میمونه و دیتا رد و بدل میشه.
useEffect(() => { const ws = new WebSocket("wss://example.com"); ws.onmessage = (event) => { console.log(event.data); }; return () => { ws.close(); }; }, []);
خیلی از باگهای اپهای realtime از همینجا میاد.
هر اتصال = یه close لازم داره
بعضی وقتا cleanup شلوغ میشه:
return () => { sub1.remove(); sub2.remove(); clearInterval(timer); };
میتونی تمیزترش کنی:
useEffect(() => { const cleanups = []; const sub = something(); cleanups.push(() => sub.remove()); const timer = setInterval(() => {}, 1000); cleanups.push(() => clearInterval(timer)); return () => { cleanups.forEach(fn => fn()); }; }, []);
دو حالت داره:
۱. وقتی کامپوننت Unmount میشه
۲. وقتی dependencyها تغییر میکنن و effect قراره دوباره اجرا بشه
React قبل از اجرای effect جدید، اول cleanup قبلی رو اجرا میکنه.
setState ساده
محاسبات داخل render
چیزایی که subscription یا async نیستن
هر چی resource خارجی یا زماندار باشه → cleanup لازم داره.
چون اگه رعایت نکنی:
❌ memory leak
❌ کند شدن اپ
❌ مصرف زیاد باتری
❌ باگهای عجیب
❌ state آپدیت روی کامپوننت حذفشده
ولی اگه رعایت کنی:
✅ اپ تمیز
✅ رفتار قابل پیشبینی
✅ performance بهتر
✅ کد حرفهایتر
هر چیزی که داخل useEffect راه میندازی از خودت بپرس:
اگه این صفحه بسته شد، باید اینو خاموش کنم یا نه؟
اگه جواب "آره" بود → حتماً cleanup بنویس. ممنون که خوندی.