• صفحه اصلی
  • مهارت‌ها
  • پروژه‌ها
  • بلاگ
  • مشاوره
  • تماس با من
مشاورهرزومه
Naser Rasouli

نویسنده

Naser Rasouli

توسعه‌دهنده فرانت‌اند؛ اینجا تجربه‌ها و یادداشت‌های واقعی‌ام از پروژه‌ها را می‌نویسم.

GitHubLinkedIn

آخرین نوشته‌ها

متدولوژی BEM در CSS: نام‌گذاری استاندارد برای کدهای تمیز
2026-02-18•1 دقیقه مطالعه

متدولوژی BEM در CSS: نام‌گذاری استاندارد برای کدهای تمیز

راهنمای عملی BEM برای جلوگیری از تداخل استایل، ساختاردهی کلاس‌ها و نگهداری ساده‌تر CSS.

بررسی نحوه به‌روزرسانی state در React
2026-02-11•1 دقیقه مطالعه

بررسی نحوه به‌روزرسانی state در React

setState بلافاصله state را عوض نمی‌کند؛ همین باعث می‌شود console.log مقدار قبلی را لاگ کند. این راهنما توضیح می‌دهد چرا و چطور مقدار جدید را درست دریافت کنیم.

مسیر مصاحبه فرانت‌اند: مراحل، انتظارها و آماده‌سازی
2026-01-17•1 دقیقه مطالعه

مسیر مصاحبه فرانت‌اند: مراحل، انتظارها و آماده‌سازی

مصاحبه فرانت‌اند فقط چند سؤال دربارهٔ HTML و JavaScript نیست؛ مسیری است برای سنجش فهم وب، حل مسئله و توان همکاری. این راهنما مراحل رایج و انتظارات هر بخش را خلاصه می‌کند.

تابع Cleanup در useEffect: از باگ‌های پنهان تا الگوهای درست

تابع Cleanup در useEffect: از باگ‌های پنهان تا الگوهای درست

2026-02-04
reactuseEffectcleanuphooks

مقدمه

تقریباً همه در React از useEffect استفاده کرده‌ایم؛ اغلب برای fetch، تایمر یا event listener. اما بخش کم‌اهمیت‌گرفته‌شدهٔ ماجرا همان cleanup است. نبود یا اشتباه‌نویسی آن باعث memory leak، لیسنرهای تکراری و خطای معروف setState on unmounted component می‌شود. در این مطلب، با نمونه‌های واقعی، نحوهٔ درست نوشتن cleanup را مرور می‌کنیم.


useEffect چه می‌کند و cleanup دقیقاً چیست؟

useEffect برای کارهایی است که مستقیماً به رندر وابسته نیستند (side effect). ساختار کلی:

useEffect(() => {
  // effect: هر کاری که بیرون از رندر لازم است

  return () => {
    // cleanup: جمع‌کردن هر منبعی که باز کرده‌ایم
  };
}, deps);

cleanup همان تابع برگشتی است که React در دو حالت اجرا می‌کند:

  • قبل از اجرای مجدد effect وقتی یکی از deps عوض شود
  • وقتی کامپوننت unmount می‌شود

در حالت Strict Mode (dev) اثرها دوبار اجرا می‌شوند تا باگ‌ها زودتر دیده شوند؛ پس نوشتن cleanup درست اهمیت بیشتری دارد.


چه زمانی cleanup اجرا می‌شود؟

  1. قبل از اجرای effect بعدی
    اگر deps تغییر کند، ابتدا cleanup مربوط به اجرای قبلی صدا زده می‌شود، بعد effect جدید اجرا می‌شود.

  2. لحظهٔ unmount
    وقتی کامپوننت از DOM حذف شود، cleanup آخرین اجرای effect فراخوانی می‌شود.

مثال رفتار ترتیب اجرا:

useEffect(() => {
  console.log("effect");
  return () => console.log("cleanup");
}, [count]);

اگر count از 0 به 1 برسد، خروجی خواهد بود: cleanup → effect.


سناریوهای ضروری cleanup

رویدادهای DOM

useEffect(() => {
  const handleResize = () => console.log(window.innerWidth);
  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);

بدون cleanup، هر بار mount جدید یا تغییر dependency، لیسنر اضافه می‌شود و چندبار فایر می‌کند.

تایمرها (setInterval / setTimeout)

useEffect(() => {
  const id = setInterval(() => console.log("tick"), 1000);
  return () => clearInterval(id);
}, []);

اگر clearInterval را ننویسید، تایمر بعد از حذف کامپوننت هم فعال می‌ماند و منابع CPU را می‌خورد.

subscription به socket/observable

useEffect(() => {
  const unsubscribe = socket.subscribe(data => {
    console.log(data);
  });

  return () => unsubscribe();
}, []);

هر subscription باید یک مسیر خروجی داشته باشد؛ در غیر این صورت، پیام‌های قدیمی همچنان به handler قبلی می‌رسند.

درخواست شبکه با AbortController

useEffect(() => {
  const controller = new AbortController();

  fetch("/api/data", { signal: controller.signal })
    .then(res => res.json())
    .then(data => console.log(data))
    .catch(err => {
      if (err.name !== "AbortError") console.error(err);
    });

  return () => controller.abort();
}, []);

با abort از setState روی کامپوننت unmounted و لاگ خطای بی‌ربط جلوگیری می‌کنید.


وابستگی‌ها و باگ‌های پنهان

  • هر بار یکی از deps تغییر کند، اول cleanup قبلی اجرا می‌شود؛ پس به refها و stateهای استابل متکی باشید.
  • اگر handler را داخل effect بسازید و آن را به dependency اضافه نکنید، نسخهٔ قدیمی handler باقی می‌ماند (stale closure). در این موارد از useCallback یا dependency صحیح استفاده کنید.
  • در Strict Mode توسعه، effect → cleanup → effect برای یک mount رخ می‌دهد؛ پس effect باید idempotent باشد و با cleanup به حالت اولیه برگردد.

اشتباهات رایج که باید ازشان دوری کرد

  • نوشتن async مستقیم روی تابع useEffect و فراموشی abort برای درخواست‌ها
  • اضافه‌کردن event listener بدون removeEventListener یا با reference متفاوت
  • تکیه بر setInterval بدون clearInterval
  • بی‌توجهی به dependency array و اتکا به مقدارهای قدیمی (stale data)
  • فرض اینکه cleanup فقط هنگام unmount اجرا می‌شود

چک‌لیست سریع قبل از مرج

  • آیا هر منبعی که باز کردید (listener، تایمر، subscription، request) مسیر جمع‌کردن دارد؟
  • آیا dependency array واقعاً تمام متغیرهای استفاده‌شده را پوشش می‌دهد یا عمداً خالی گذاشته شده؟
  • آیا در dev با Strict Mode، effect و cleanup چندبار اجرا می‌شوند و رفتارتان همچنان صحیح است؟
  • آیا handlerها و callbackها stable هستند تا removeEventListener کار کند؟

جمع‌بندی

تابع cleanup کوچک است اما جلوی باگ‌های بزرگ را می‌گیرد. هر بار useEffect می‌نویسید از خودتان بپرسید: «چه چیزی راه انداختم که باید خاموشش کنم؟» پاسخ به همین سؤال ساده، اپلیکیشن React را از memory leak، listenerهای تکراری و خطاهای setState نجات می‌دهد.