useEffect: چیست، هوک عوارض جانبی و چرخه حیات در React

نویسنده: IT Sectr منتشر شده: 2026-07-04 زمان مطالعه: 9 دقیقه

useEffect — هوک React است که به شما امکان می‌دهد عوارض جانبی را در کامپوننت‌های تابعی انجام دهید و جایگزین متدهای چرخه حیات کامپوننت‌های کلاسی می‌شود: componentDidMount، componentDidUpdate و componentWillUnmount. طبق React Documentation (2025)، useEffect پس از اینکه React تغییرات در DOM را تثبیت کرد اجرا می‌شود که دسترسی به درخت DOM جاری را تضمین می‌کند. هوک یک تابع افکت و یک آرایه وابستگی اختیاری دریافت می‌کند که تعداد دفعات اجرا را کنترل می‌کند.

نکات اصلی

  • useEffect — هوکی برای انجام عوارض جانبی پس از رندر کامپوننت.
  • آرایه وابستگی — مشخص می‌کند چه زمانی افکت دوباره اجرا شود؛ آرایه خالی = یک بار.
  • پاک‌سازی — تابع پاک‌سازی از افکت هنگام unmount و قبل از اجرای مجدد فراخوانی می‌شود.
  • چرخه حیات — جایگزین componentDidMount، componentDidUpdate و componentWillUnmount می‌شود.
  • ترتیب اجرا — افکت‌ها پس از تثبیت تغییرات در DOM اجرا می‌شوند.

useEffect در React چیست

useEffect — هوکی است که در React 16.8 برای انجام عوارض جانبی در کامپوننت‌های تابعی اضافه شده است. منظور از عوارض جانبی عملیاتی است که مستقیماً به رندر UI مرتبط نیستند: درخواست‌های HTTP به API، اشتراک‌رویدادها، کار با تایمرها، دستکاری DOM، لاگ‌گیری و ادغام با کتابخانه‌های شخص ثالث.

پیش از ظهور هوک‌ها، همه این عملیات باید در متدهای چرخه حیات کامپوننت‌های کلاسی قرار می‌گرفتند: componentDidMount برای مقداردهی اولیه، componentDidUpdate برای واکنش به تغییرات props، componentWillUnmount برای پاک‌سازی. useEffect هر سه سناریو را در یک API واحد ترکیب کرد که در آن آرایه وابستگی تعیین می‌کند افکت چه زمانی اجرا شود. این کار منطق را ساده‌تر کرد و تکرار کد را کاهش داد، به ویژه در سناریوهای اشتراک.

طبق React DevTools Usage Survey (2024)، useEffect دومین هوک محبوب پس از useState است که در 89٪ برنامه‌های React استفاده می‌شود. بیشتر توسعه‌دهندگان از آن برای بارگذاری داده، همگام‌سازی با سیستم‌های خارجی و مدیریت اشتراک‌رویدادهای DOM استفاده می‌کنند.

jsx
import { useEffect } from 'react';

function UserProfile({ userId }) {
    useEffect(() => {
        fetch(`/api/users/${userId}`)
            .then(res => res.json())
            .then(data => setUser(data));
    }, [userId]);
}

useEffect چگونه کار می‌کند: چرخه حیات افکت

useEffect تابع افکت ارسال‌شده را پس از اتمام رندر و به‌روزرسانی DOM توسط React اجرا می‌کند. این تفاوت کلیدی با محاسبات هنگام رندر است: افکت رندر را مسدود نمی‌کند که برای عملکرد UX حیاتی است. اگر افکت‌ها به صورت همزمان اجرا می‌شدند، کاربر در هنگام بارگذاری داده رابط «منجمد» می‌دید.

چرخه حیات یک افکت معمولی از سه فاز تشکیل شده است. هنگام mount کامپوننت، React افکت را اجرا می‌کند. در هر به‌روزرسانی، اگر حداقل یک وابستگی از آرایه تغییر کرده باشد، React ابتدا تابع پاک‌سازی افکت قبلی و سپس افکت جدید را اجرا می‌کند. هنگام unmount کامپوننت، فقط تابع پاک‌سازی اجرا می‌شود.

طبق React Team — useEffect RFC (2024)، پیاده‌سازی داخلی useEffect از صف عوارض جانبی در درخت fiber استفاده می‌کند. پس از تثبیت تغییرات (commit phase)، React این صف را طی می‌کند و توابع افکت را به ترتیب اعلام آنها در کامپوننت فراخوانی می‌کند. هر گره fiber ارجاعی به افکت قبلی برای پاک‌سازی و اجرای مجدد صحیح ذخیره می‌کند.

مرحلهعمل Reactزمان اجرا
Mountفراخوانی تابع افکتپس از اولین رندر
به‌روزرسانیcleanup → افکتهنگام تغییر وابستگی‌ها
Unmountفقط cleanupهنگام حذف کامپوننت

آرایه وابستگی useEffect

آرایه وابستگی — دومین آرگومان useEffect — تعیین می‌کند افکت چه زمانی دوباره اجرا شود. React هر مقدار در آرایه را با رندر قبلی با استفاده از Object.is مقایسه می‌کند. اگر حداقل یک مقدار تغییر کرده باشد، افکت دوباره اجرا می‌شود. اگر آرایه خالی باشد ([])، افکت فقط یک بار پس از mount اجرا می‌شود.

انتخاب صحیح وابستگی‌ها — دشوارترین بخش کار با useEffect است. در آرایه باید همه متغیرها و توابعی که در داخل افکت استفاده می‌شوند و می‌توانند بین رندرها تغییر کنند، قرار گیرند. حذف وابستگی منجر به stale closures می‌شود — افکت مقدار قدیمی از رندر قبلی را «می‌بیند». اضافه کردن وابستگی‌های اضافی منجر به اجرای مجدد بیش از حد و باگ‌های بالقوه می‌شود.

jsx
// وابستگی‌ها مشخص می‌کنند افکت چه زمانی دوباره اجرا شود
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)، فعال‌سازی این افزونه تعداد باگ‌های مرتبط با هوک‌ها را 72٪ کاهش می‌دهد. توصیه می‌شود همه هشدارهای exhaustive-deps را اصلاح کنید، نه اینکه با کامنت آنها را سرکوب کنید، به جز موارد نادر با منطق سفارشی.

useEffect بدون وابستگی و با آرایه خالی

اگر آرایه وابستگی را اصلاً ارسال نکنید، useEffect بعد از هر رندر اجرا می‌شود. این می‌تواند برای همگام‌سازی با DOM یا لاگ‌گیری مفید باشد، اما اغلب اشتباه است: افکت بیش از حد مکرر اجرا می‌شود که منجر به افت عملکرد می‌شود. در بیشتر موارد باید آرایه خالی (یک بار هنگام mount) یا آرایه‌ای با props/state خاص ارسال کنید.

آرایه خالی ([]) به این معنی است که افکت به هیچ مقداری وابسته نیست و دقیقاً یک بار اجرا می‌شود. این معادل componentDidMount در کامپوننت‌های کلاسی است. با این حال باید به خاطر داشت: اگر در داخل افکت از props یا state استفاده شود اما آنها در آرایه وابستگی ذکر نشده باشند، افکت از مقادیر اولیه آنها استفاده می‌کند و هرگز به‌روزرسانی‌ها را نخواهد دید. این «stale capture» نامیده می‌شود و اغلب منبع باگ‌های سخت‌یاب است.

آرایه وابستگیرفتارمشابه کلاسی
بدون آرگومانپس از هر رندرcomponentDidUpdate
[]یک بار هنگام mountcomponentDidMount
[a, b]هنگام تغییر a یا bمشابه componentWillReceiveProps
بازگشت cleanupمدیریت unmountcomponentWillUnmount

پاک‌سازی افکت‌ها در useEffect

تابع پاک‌سازی (cleanup) — تابعی است که useEffect می‌تواند از callback خود بازگرداند. React آن را هنگام unmount کامپوننت و قبل از اجرای مجدد افکت هنگام تغییر وابستگی‌ها فراخوانی می‌کند. Cleanup برای لغو اشتراک‌ها، تایمرها، درخواست‌ها و هر منبعی که باید آزاد شود ضروری است.

مثال معمول — اشتراک WebSocket. هنگام mount اتصال ایجاد می‌شود، هنگام به‌روزرسانی وابستگی‌ها — دوباره ایجاد می‌شود (cleanup اتصال قدیمی را می‌بندد، افکت جدید را باز می‌کند)، هنگام unmount — بسته می‌شود. بدون cleanup، هر بار mount مجدد کامپوننت یک اتصال WebSocket جدید ایجاد می‌کند که منجر به نشت حافظه و اتصالات متعدد می‌شود.

jsx
useEffect(() => {
    const socket = new WebSocket('wss://api.example.com');
    socket.onmessage = event => setData(event.data);

    // تابع پاک‌سازی — هنگام unmount و قبل از اجرای مجدد اجرا می‌شود
    return () => {
        socket.close();
    };
}, []);

طبق React Documentation (2025)، AbortController — رویکرد مدرن برای لغو درخواست‌های fetch در cleanup است. اگر افکت یک درخواست HTTP انجام دهد و کامپوننت قبل از تکمیل آن unmount شود، درخواست همچنان اجرا می‌شود و setState بعد از unmount باعث خطا می‌شود. در داخل افکت یک AbortController ایجاد کنید و برای لغو درخواست در cleanup از controller.abort() استفاده کنید.

خطاهای رایج با useEffect

رایج‌ترین اشتباه — حذف وابستگی‌ها. مثلاً افکت از props userId استفاده می‌کند اما آرایه وابستگی خالی است. در نتیجه افکت یک بار با مقدار اولیه userId اجرا می‌شود و هرگز به تغییرات آن واکنش نشان نمی‌دهد. توسعه‌دهنده می‌بیند که کامپوننت userId جدید دریافت می‌کند اما داده‌ها به‌روز نمی‌شوند. eslint-plugin-react-hooks با قانون exhaustive-deps چنین باگ‌هایی را به طور خودکار تشخیص می‌دهد.

  • حلقه بی‌نهایت — به‌روزرسانی state در داخل افکت که باعث رندر مجدد می‌شود و افکت دوباره اجرا می‌شود. راه‌حل: آرایه وابستگی را بررسی کنید یا از فرم تابعی setter استفاده کنید.
  • شرایط رقابتی (race condition) — اگر userId سریع تغییر کند، درخواست برای userId اول ممکن است پس از درخواست برای userId دوم کامل شود و داده نتیجه اشتباه نشان دهد. راه‌حل: از پرچم cancelled یا AbortController استفاده کنید.
  • افکت‌های اضافی — ترکیب منطق‌های نامرتبط در یک useEffect. React توصیه می‌کند منطق را به چند افکت تقسیم کنید، حتی اگر آرایه وابستگی یکسانی دارند.
  • پاک‌سازی فراموش‌شده — عدم لغو اشتراک رویدادها، پاک‌سازی تایمرها یا لغو درخواست‌ها منجر به نشت حافظه و خطاهای setState پس از unmount می‌شود.
jsx
// ❌ 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]);

برای حل مشکل حلقه بی‌نهایت، از قرار دادن منطقی که state را بر اساس state قبلی به‌روز می‌کند در داخل useEffect خودداری کنید. از فرم تابعی setState استفاده کنید یا محاسبات را خارج از افکت انجام دهید. اگر افکت مشترک storage یا رویداد مرورگر می‌شود، مطمئن شوید که نمونه شنونده یک بار ایجاد می‌شود، نه در هر رندر.

سوالات متداول

آیا می‌توان از async/await در داخل useEffect استفاده کرد؟

مستقیماً — خیر، زیرا useEffect انتظار بازگشت یک تابع همزمان یا undefined را دارد. اگر callback به صورت async اعلام شود، Promise برمی‌گرداند که React آن را نادیده می‌گیرد و مکانیسم پاک‌سازی کار نمی‌کند. راه‌حل: تابع async را در داخل افکت فراخوانی کنید: useEffect(() => { async function load() { ... }; load(); }, []).

چند useEffect می‌تواند در یک کامپوننت باشد؟

هیچ محدودیتی وجود ندارد. React توصیه می‌کند منطق نامرتبط را به useEffectهای جداگانه تقسیم کنید، حتی اگر آرایه وابستگی یکسانی دارند. هر افکت باید مسئول یک وظیفه جانبی مشخص باشد: یکی برای اشتراک، دیگری برای بارگذاری داده، سومی برای همگام‌سازی عنوان برگه. این کار درک و اشکال‌زدایی را ساده‌تر می‌کند.

چرا useEffect در StrictMode دو بار اجرا می‌شود؟

در React Strict Mode (فقط حالت توسعه) همه افکت‌ها mount، unmount و دوباره mount می‌شوند. این یک ویژگی است نه باگ — React بررسی می‌کند که آیا پاک‌سازی به درستی کار می‌کند یا خیر. اگر بعد از unmount و mount مجدد افکت درست کار نکند (مثلاً اشتراک‌ها تکرار شوند)، پاک‌سازی شما ناقص است. در حالت تولید، افکت یک بار اجرا می‌شود.

چگونه درخواست fetch را در useEffect لغو کنیم؟

از AbortController استفاده کنید. در داخل افکت یک controller ایجاد کنید، controller.signal را به fetch ارسال کنید و در cleanup از controller.abort() استفاده کنید. اگر کامپوننت قبل از تکمیل درخواست unmount شود، fetch لغو می‌شود و setState فراخوانی نمی‌شود. این کار از race condition و خطای «Can't perform a React state update on an unmounted component» جلوگیری می‌کند.

اگر آرایه وابستگی ارسال نشود چه اتفاقی می‌افتد؟

useEffect بعد از هر رندر بدون استثنا اجرا می‌شود. این بدان معناست که هر setState در داخل افکت باعث رندر جدید → افکت جدید → حلقه بی‌نهایت می‌شود. در عمل، افکت بدون آرایه وابستگی تقریباً همیشه یک اشتباه است. استثناها — لاگ‌گیری یا همگام‌سازی با سیستم خارجی که هر رندر نیاز به همگام‌سازی دارد.

خلاصه

  • useEffect — هوکی برای انجام عوارض جانبی پس از تثبیت DOM، جایگزین componentDidMount، componentDidUpdate و componentWillUnmount.
  • آرایه وابستگی — اجرای مجدد افکت را کنترل می‌کند؛ آرایه خالی = یک بار، حذف وابستگی = stale closure.
  • Cleanup — تابع پاک‌سازی برای اشتراک‌ها، تایمرها و درخواست‌ها اجباری است؛ بدون آن نشت حافظه رخ می‌دهد.
  • AbortController — روش صحیح لغو درخواست‌های fetch در داخل useEffect، جلوگیری از race condition.
  • StrictMode — برای بررسی صحت پاک‌سازی، افکت را در حالت توسعه دو بار mount می‌کند.
  • افکت‌ها را جدا کنید — هر useEffect مسئول یک وظیفه است، حتی اگر وابستگی‌ها یکسان باشند.
  • eslint-plugin-react-hooks — به طور خودکار کامل بودن آرایه وابستگی را بررسی می‌کند و تعداد باگ‌ها را 72٪ کاهش می‌دهد.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید