useEffect — هوک React است که به شما امکان میدهد عوارض جانبی را در کامپوننتهای تابعی انجام دهید و جایگزین متدهای چرخه حیات کامپوننتهای کلاسی میشود: componentDidMount، componentDidUpdate و componentWillUnmount. طبق React Documentation (2025)، useEffect پس از اینکه React تغییرات در DOM را تثبیت کرد اجرا میشود که دسترسی به درخت DOM جاری را تضمین میکند. هوک یک تابع افکت و یک آرایه وابستگی اختیاری دریافت میکند که تعداد دفعات اجرا را کنترل میکند.
نکات اصلی
useEffect — هوکی است که در React 16.8 برای انجام عوارض جانبی در کامپوننتهای تابعی اضافه شده است. منظور از عوارض جانبی عملیاتی است که مستقیماً به رندر UI مرتبط نیستند: درخواستهای HTTP به API، اشتراکرویدادها، کار با تایمرها، دستکاری DOM، لاگگیری و ادغام با کتابخانههای شخص ثالث.
پیش از ظهور هوکها، همه این عملیات باید در متدهای چرخه حیات کامپوننتهای کلاسی قرار میگرفتند: componentDidMount برای مقداردهی اولیه، componentDidUpdate برای واکنش به تغییرات props، componentWillUnmount برای پاکسازی. useEffect هر سه سناریو را در یک API واحد ترکیب کرد که در آن آرایه وابستگی تعیین میکند افکت چه زمانی اجرا شود. این کار منطق را سادهتر کرد و تکرار کد را کاهش داد، به ویژه در سناریوهای اشتراک.
طبق React DevTools Usage Survey (2024)، useEffect دومین هوک محبوب پس از 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 تابع افکت ارسالشده را پس از اتمام رندر و بهروزرسانی DOM توسط React اجرا میکند. این تفاوت کلیدی با محاسبات هنگام رندر است: افکت رندر را مسدود نمیکند که برای عملکرد UX حیاتی است. اگر افکتها به صورت همزمان اجرا میشدند، کاربر در هنگام بارگذاری داده رابط «منجمد» میدید.
چرخه حیات یک افکت معمولی از سه فاز تشکیل شده است. هنگام mount کامپوننت، React افکت را اجرا میکند. در هر بهروزرسانی، اگر حداقل یک وابستگی از آرایه تغییر کرده باشد، React ابتدا تابع پاکسازی افکت قبلی و سپس افکت جدید را اجرا میکند. هنگام unmount کامپوننت، فقط تابع پاکسازی اجرا میشود.
طبق React Team — useEffect RFC (2024)، پیادهسازی داخلی useEffect از صف عوارض جانبی در درخت fiber استفاده میکند. پس از تثبیت تغییرات (commit phase)، React این صف را طی میکند و توابع افکت را به ترتیب اعلام آنها در کامپوننت فراخوانی میکند. هر گره fiber ارجاعی به افکت قبلی برای پاکسازی و اجرای مجدد صحیح ذخیره میکند.
| مرحله | عمل React | زمان اجرا |
|---|---|---|
| Mount | فراخوانی تابع افکت | پس از اولین رندر |
| بهروزرسانی | cleanup → افکت | هنگام تغییر وابستگیها |
| Unmount | فقط cleanup | هنگام حذف کامپوننت |
آرایه وابستگی — دومین آرگومان useEffect — تعیین میکند افکت چه زمانی دوباره اجرا شود. React هر مقدار در آرایه را با رندر قبلی با استفاده از Object.is مقایسه میکند. اگر حداقل یک مقدار تغییر کرده باشد، افکت دوباره اجرا میشود. اگر آرایه خالی باشد ([])، افکت فقط یک بار پس از mount اجرا میشود.
انتخاب صحیح وابستگیها — دشوارترین بخش کار با 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)، فعالسازی این افزونه تعداد باگهای مرتبط با هوکها را 72٪ کاهش میدهد. توصیه میشود همه هشدارهای exhaustive-deps را اصلاح کنید، نه اینکه با کامنت آنها را سرکوب کنید، به جز موارد نادر با منطق سفارشی.
اگر آرایه وابستگی را اصلاً ارسال نکنید، useEffect بعد از هر رندر اجرا میشود. این میتواند برای همگامسازی با DOM یا لاگگیری مفید باشد، اما اغلب اشتباه است: افکت بیش از حد مکرر اجرا میشود که منجر به افت عملکرد میشود. در بیشتر موارد باید آرایه خالی (یک بار هنگام mount) یا آرایهای با props/state خاص ارسال کنید.
آرایه خالی ([]) به این معنی است که افکت به هیچ مقداری وابسته نیست و دقیقاً یک بار اجرا میشود. این معادل componentDidMount در کامپوننتهای کلاسی است. با این حال باید به خاطر داشت: اگر در داخل افکت از props یا state استفاده شود اما آنها در آرایه وابستگی ذکر نشده باشند، افکت از مقادیر اولیه آنها استفاده میکند و هرگز بهروزرسانیها را نخواهد دید. این «stale capture» نامیده میشود و اغلب منبع باگهای سختیاب است.
| آرایه وابستگی | رفتار | مشابه کلاسی |
|---|---|---|
| بدون آرگومان | پس از هر رندر | componentDidUpdate |
| [] | یک بار هنگام mount | componentDidMount |
| [a, b] | هنگام تغییر a یا b | مشابه componentWillReceiveProps |
| بازگشت cleanup | مدیریت unmount | componentWillUnmount |
تابع پاکسازی (cleanup) — تابعی است که useEffect میتواند از callback خود بازگرداند. React آن را هنگام unmount کامپوننت و قبل از اجرای مجدد افکت هنگام تغییر وابستگیها فراخوانی میکند. Cleanup برای لغو اشتراکها، تایمرها، درخواستها و هر منبعی که باید آزاد شود ضروری است.
مثال معمول — اشتراک WebSocket. هنگام mount اتصال ایجاد میشود، هنگام بهروزرسانی وابستگیها — دوباره ایجاد میشود (cleanup اتصال قدیمی را میبندد، افکت جدید را باز میکند)، هنگام unmount — بسته میشود. بدون cleanup، هر بار mount مجدد کامپوننت یک اتصال WebSocket جدید ایجاد میکند که منجر به نشت حافظه و اتصالات متعدد میشود.
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() استفاده کنید.
رایجترین اشتباه — حذف وابستگیها. مثلاً افکت از 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]);
برای حل مشکل حلقه بینهایت، از قرار دادن منطقی که state را بر اساس state قبلی بهروز میکند در داخل useEffect خودداری کنید. از فرم تابعی setState استفاده کنید یا محاسبات را خارج از افکت انجام دهید. اگر افکت مشترک storage یا رویداد مرورگر میشود، مطمئن شوید که نمونه شنونده یک بار ایجاد میشود، نه در هر رندر.
سوالات متداول
مستقیماً — خیر، زیرا useEffect انتظار بازگشت یک تابع همزمان یا undefined را دارد. اگر callback به صورت async اعلام شود، Promise برمیگرداند که React آن را نادیده میگیرد و مکانیسم پاکسازی کار نمیکند. راهحل: تابع async را در داخل افکت فراخوانی کنید: useEffect(() => { async function load() { ... }; load(); }, []).
هیچ محدودیتی وجود ندارد. React توصیه میکند منطق نامرتبط را به useEffectهای جداگانه تقسیم کنید، حتی اگر آرایه وابستگی یکسانی دارند. هر افکت باید مسئول یک وظیفه جانبی مشخص باشد: یکی برای اشتراک، دیگری برای بارگذاری داده، سومی برای همگامسازی عنوان برگه. این کار درک و اشکالزدایی را سادهتر میکند.
در React Strict Mode (فقط حالت توسعه) همه افکتها mount، unmount و دوباره mount میشوند. این یک ویژگی است نه باگ — React بررسی میکند که آیا پاکسازی به درستی کار میکند یا خیر. اگر بعد از unmount و mount مجدد افکت درست کار نکند (مثلاً اشتراکها تکرار شوند)، پاکسازی شما ناقص است. در حالت تولید، افکت یک بار اجرا میشود.
از 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 در داخل افکت باعث رندر جدید → افکت جدید → حلقه بینهایت میشود. در عمل، افکت بدون آرایه وابستگی تقریباً همیشه یک اشتباه است. استثناها — لاگگیری یا همگامسازی با سیستم خارجی که هر رندر نیاز به همگامسازی دارد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید