در توسعهی رابط کاربری با React، یکی از چالشهای رایج، مدیریت عملیاتهای جانبی یا همان Side Effects است؛ کارهایی که خارج از چرخهی معمول رندر انجام میشوند مانند: فراخوانی API، تغییر در DOM یا ثبت اشتراکها. ریاکت برای مدیریت این عملیاتها، هوکهای قدرتمندی مانند useEffect
و useLayoutEffect
را معرفی کرده که هریک رفتار، زمانبندی، و تأثیر خاص خود را بر رندرینگ و پرفورمنس اپلیکیشن دارند.
در نگاه اول این دو هوک ممکن است بسیار شبیه به هم بهنظر برسند، اما تفاوت ظریفی که در زمان اجرای آنها نسبت به فرایند paint صفحه وجود دارد، میتواند بهطور مستقیم بر تجربهی کاربر و روانی رابط کاربری تأثیر بگذارد. درک دقیق این تفاوت، بهویژه در پروژههای پیچیده یا تعاملی، نقشی کلیدی در جلوگیری از مشکلاتی مانند Flicker، پرش المانها یا کندی رابط کاربری ایفا میکند.
در این مقاله از وبسایت راکت، با زبانی ساده و مثالهای کاربردی، به بررسی تفاوت useEffect و useLayoutEffect در ریاکت میپردازیم، زمان اجرای آنها را تحلیل میکنیم، کاربردهای مناسب هر کدام را مشخص میسازیم و در نهایت با مثالهای واقعی، به شما نشان میدهیم که در چه شرایطی باید از هر کدام استفاده کرد تا بهترین عملکرد و تجربه کاربری حاصل شود.
مقدمهای بر هوکهای Effect در ری اکت
در ری اکت، کامپوننتها بهصورت تابعی تعریف میشوند و در حالت ایدهآل تنها به ورودیها (props و state) واکنش نشان میدهند. اما در عمل، بسیاری از رفتارهای مورد نیاز در اپلیکیشنها در قالب «عملیات جانبی» (Side Effects) بروز پیدا میکنند؛ عملیاتی که خارج از فرآیند اصلی رندرینگ اتفاق میافتند و باید بهنحوی کنترل شوند. اینجاست که هوکهای Effect مانند useEffect
و useLayoutEffect
وارد عمل میشوند.
هوکهای Effect برای چه کاری استفاده میشوند؟ (Side Effects)
هوکهای Effect ابزاری هستند برای اجرای کدهایی که مستقیماً به تغییر یا خواندن دادههایی خارج از JSX مربوط میشوند. این کدها اغلب شامل عملیاتهایی نظیر:
-
فراخوانی APIها و دریافت اطلاعات
-
دستکاری در DOM (مثلاً تغییر اندازه یا موقعیت یک عنصر)
-
تنظیم تایمرها یا رویدادهای خارجی مانند scroll یا resize
-
ایجاد و پاکسازی اشتراکها (subscriptions)
میشوند. هدف اصلی این هوکها، مدیریت Side Effectها در یک ساختار تابعی است، بدون نیاز به استفاده از کلاسها و متدهای چرخه عمر کلاسکامپوننتها.
مفهوم Side Effect در کامپوننتهای ری اکت
در ری اکت، کامپوننتها باید در حد امکان «خالص» (pure) باشند؛ به این معنی که خروجی آنها تنها به ورودیها بستگی داشته باشد. با این حال، در دنیای واقعی، بسیاری از رفتارهای کاربردی کامپوننتها به صورت Side Effect پیادهسازی میشوند.
Side Effect به هر عملی اطلاق میشود که:
-
بر محیط بیرون از کامپوننت تأثیر بگذارد (مانند تغییر در DOM یا localStorage)
-
یا به وضعیت بیرونی وابسته باشد (مانند دریافت داده از سرور)
React اجرای این کدها را درون توابع معمولی محدود میکند تا از ناسازگاری و رفتارهای پیشبینینشده جلوگیری شود. در نتیجه باید از هوکهایی استفاده کرد که بهصورت کنترلشده، پس از رندر، این عملیاتها را اجرا کنند.
چرا به هوکهایی مانند useEffect و useLayoutEffect نیاز داریم؟
قبل از هوکها، توسعهدهندگان React مجبور بودند از متدهایی مانند componentDidMount
،componentDidUpdate
و componentWillUnmount
برای مدیریت Side Effectها استفاده کنند. اما این روشها فقط در کلاسکامپوننتها در دسترس بودند و باعث پیچیدگی و تکرار کد میشدند.
با معرفی هوکهای ری اکت، بهویژه useEffect
و useLayoutEffect
، امکان مدیریت Side Effectها در کامپوننتهای تابعی فراهم شد؛ آن هم به شکلی ساده، شفاف، و انعطافپذیر.
تفاوت کلیدی بین این دو هوک در زمانبندی اجرا و تأثیر آنها بر فرآیند رندرینگ و Paint صفحه است. در ادامه، هر یک از این دو هوک را بهصورت جداگانه بررسی خواهیم کرد تا بتوانید در شرایط واقعی بهترین انتخاب را داشته باشید.
آشنایی با useEffect: اجرای غیرهمزمان پس از Paint
useEffect زمانی اجرا میشود که کامپوننت رندر شده و محتوای آن توسط مرورگر روی صفحه «paint» شده باشد. همین ویژگی ساده اما مهم، آن را به گزینهای مناسب برای بسیاری از نیازهای معمول در توسعهی رابط کاربری تبدیل کرده است.
نحوه عملکرد useEffect به زبان ساده
زمانی که از useEffect استفاده میکنید، ریاکت ابتدا کامپوننت را رندر میکند و خروجی JSX را در DOM قرار میدهد. سپس، در مرحلهای مجزا و غیربلاککننده، تابعی که داخل useEffect تعریف کردهاید اجرا میشود. این یعنی هر چیزی که در useEffect بنویسید، هیچ تأخیری در نمایش اولیهی صفحه برای کاربر ایجاد نمیکند.
زمانبندی اجرای useEffect: پس از رندر و paint شدن صفحه در مرورگر
در اصطلاح دقیقتر، useEffect در فاز "commit" ریاکت اجرا نمیشود، بلکه پس از آن، در مرحلهای که مرورگر وقت آزاد دارد، اجرا میگردد. این باعث میشود تغییرات داخل آن، نهتنها بلاککنندهی paint نباشند، بلکه در صورت نیاز به عملکرد سریع در رندر اولیه، بهینهتر هم عمل کنند.
مزیت اجرای غیرهمزمان: جلوگیری از مسدود شدن UI
اجرای غیرهمزمان به این معناست که مرورگر مجبور نیست قبل از نمایش صفحه، منتظر اجرای کدهای شما بماند. در نتیجه:
- رابط کاربری سریعتر نمایش داده میشود.
- احساس روانبودن اپلیکیشن افزایش مییابد.
- کاربر کمتر با تأخیر یا فریز شدن مواجه میشود.
به همین دلیل، اگر Side Effect شما نیازمند دستکاری بصری فوری در DOM نیست، useEffect تقریباً همیشه گزینهی مناسبتری نسبت به useLayoutEffect است.
مثال عملی ساده از useEffect
فرض کنید میخواهید هنگام لود شدن یک صفحه، عنوان (title) تب مرورگر را تغییر دهید و همزمان دادهای از یک API دریافت کنید. میتوانید از useEffect
به این صورت استفاده کنید:
import { useEffect, useState } from "react";
function Example() {
const [data, setData] = useState(null);
useEffect(() => {
document.title = "Dashboard";
fetch("https://api.example.com/data")
.then((res) => res.json())
.then((json) => setData(json));
}, []);
return <div>{data ? <p>{data.message}</p> : <p>Loading...</p>}</div>;
}
در این مثال، هیچکدام از عملیاتها (تغییر title یا fetch داده) نیازی به اجرای فوری در مرحلهی رندر ندارند. بنابراین اجرای آنها پس از paint باعث روان ماندن رابط کاربری میشود.
آرایه وابستگیها (Dependency Array) در useEffect و نقش آن
آرگومان دوم useEffect
که بهصورت یک آرایه تعریف میشود، به ریاکت اعلام میکند که چه زمانی باید این Effect دوباره اجرا شود. این آرایه سه کاربرد رایج دارد:
-
[]
(آرایه خالی): فقط یکبار در mount اولیه اجرا میشود. -
[x, y]
: هر بار که یکی از مقادیر x یا y تغییر کند، Effect دوباره اجرا میشود. -
بدون آرایه: بعد از هر رندر کامپوننت، بدون استثنا اجرا میشود.
استفادهی درست از آرایهی وابستگیها نهتنها برای کارکرد صحیح اپلیکیشن ضروری است، بلکه برای جلوگیری از اجرایهای غیرضروری و افزایش کارایی نیز اهمیت دارد.
آشنایی با useLayoutEffect: اجرای همزمان قبل از Paint
در شرایط خاصی، اجرای عملیات پس از رندر اما قبل از نمایش محتوا به کاربر ضروری میشود (بهویژه زمانی که تغییرات بصری به موقعیت، اندازه یا ساختار ظاهری عناصر وابستهاند). در این موقعیتها، هوک useLayoutEffect
ابزار مناسبی است. برخلاف useEffect
، این هوک بلافاصله پس از commit ریاکت و قبل از آنکه مرورگر صفحه را paint کند اجرا میشود.
نحوه عملکرد useLayoutEffect به زبان ساده
useLayoutEffect شباهت ساختاری زیادی با useEffect دارد، اما با یک تفاوت کلیدی: زمان اجرا. هنگامی که یک کامپوننت رندر میشود، ری اکت ابتدا DOM را بهروزرسانی میکند. درست در همین لحظه، قبل از آنکه مرورگر فرصت کند چیزی را به کاربر نمایش دهد، useLayoutEffect اجرا میشود. این یعنی هر تغییری که در این مرحله در DOM ایجاد میکنید، قبل از paint اعمال میشود و کاربر هیچ فلیکر یا پرشی را مشاهده نمیکند.
زمانبندی اجرای useLayoutEffect
فرآیند اجرا بهطور خلاصه به این صورت است:
- React کامپوننت را رندر میکند.
- DOM بهروزرسانی میشود.
- useLayoutEffect اجرا میشود.
- مرورگر paint را انجام میدهد و نتیجهی نهایی را به کاربر نمایش میدهد.
این ترتیب اجرا، اجازه میدهد تا:
- ابعاد و موقعیت دقیق المانها خوانده شود.
- تغییرات استایل فوری و بدون تأخیر اعمال شود.
- از بروز حالتهایی مانند پرش بصری (flicker) جلوگیری شود.
ماهیت همزمان (Synchronous) و تأثیر آن بر رندرینگ
بر خلاف useEffect که غیربلاککننده است، useLayoutEffect کاملاً همزمان (synchronous) اجرا میشود. این یعنی مرورگر باید صبر کند تا اجرای کامل تابع useLayoutEffect به پایان برسد، سپس paint را انجام دهد. در نتیجه، استفادهی غیرضروری یا سنگین از آن میتواند باعث کاهش پرفورمنس یا مسدود شدن رندر شود.
نکتهٔ کلیدی اینجاست که باید فقط زمانی از useLayoutEffect
استفاده کرد که اجرای دقیق و فوری پیش از paint ضروری باشد.
مثال عملی ساده از useLayoutEffect
فرض کنید میخواهید یک tooltip را نمایش دهید و باید موقعیت آن را بر اساس ابعاد واقعی دکمه (trigger) محاسبه کنید. اگر از useEffect استفاده کنید، محاسبه پس از paint انجام میشود و ممکن است کاربر پرش بصری را مشاهده کند. با useLayoutEffect، میتوانید پیش از paint موقعیت را محاسبه و تنظیم کنید:
import { useLayoutEffect, useRef, useState } from "react";
function TooltipExample() {
const buttonRef = useRef(null);
const [tooltipStyle, setTooltipStyle] = useState({});
useLayoutEffect(() => {
const rect = buttonRef.current.getBoundingClientRect();
setTooltipStyle({
position: "absolute",
top: rect.bottom + 8 + "px",
left: rect.left + "px",
});
}, []);
return (
<div style={{ position: "relative" }}>
<button ref={buttonRef}>Hover me</button>
<div style={tooltipStyle} className="tooltip">Tooltip text</div>
</div>
);
}
در این مثال، چون موقعیت tooltip دقیقاً وابسته به ابعاد دکمه است، استفاده از useLayoutEffect
مانع از آن میشود که tooltip ابتدا در مکان اشتباهی ظاهر شود و سپس بپرد به جای درست—اتفاقی که در useEffect
رخ میدهد و باعث flicker میشود.
تفاوتهای کلیدی بین useEffect و useLayoutEffect
پس از آشنایی جداگانه با دو هوک مهم React برای مدیریت Side Effect، وقت آن رسیده که تفاوتهای کلیدی آنها را در کنار هم بررسی کنیم. گرچه در ظاهر API این دو یکسان است، اما تفاوت زمان اجرا، تأثیر بر رندر و عملکرد، و همچنین کاربردهای مناسب، آنها را به ابزارهایی کاملاً متفاوت در عمل تبدیل میکند.
زمان اجرا (Timing): تفاوت اصلی و حیاتی
-
useEffect
: پس از paint شدن DOM توسط مرورگر اجرا میشود. یعنی ابتدا همهچیز رندر و به کاربر نمایش داده میشود، سپس Effect اجرا میگردد. -
useLayoutEffect
: قبل از paint شدن صفحه و بلافاصله پس از commit شدن DOM توسط React اجرا میشود. بنابراین هر تغییری در این مرحله، قبل از آنکه کاربر چیزی ببیند، در DOM اعمال شده است.
✅ این تفاوت در زمان اجرا، مبنای اصلی تمام تفاوتهای دیگر است.
مسدود کردن رندر (Blocking vs. Non-blocking)
useEffect
: غیربلاککننده (non-blocking) است. مرورگر برای اجرای آن صبر نمیکند، بنابراین رندر و نمایش UI بدون وقفه انجام میشود.useLayoutEffect
: بلاککننده (blocking) است. اجرای آن قبل از paint صورت میگیرد، بنابراین مرورگر تا پایان اجرای این هوک، نمایش UI را به تأخیر میاندازد.
❗ اگر عملیات سنگینی در useLayoutEffect انجام دهید، میتواند باعث تأخیر در رندر شدن صفحه یا حتی کاهش FPS شود.
تأثیر بر عملکرد (Performance)
- چون
useLayoutEffect
در مسیر حیاتی رندر اجرا میشود، استفادهی بیدلیل از آن باعث افت عملکرد محسوس میشود—بهخصوص در کامپوننتهای پیچیده یا با رندر مجدد مکرر. useEffect
با اجرای Deferred (مؤخر) و غیربلاککننده، در اکثر موارد عملکرد بهتری ارائه میدهد و برای بیشتر use caseها مناسبتر است.
📌 قاعدهی کلی: تا زمانی که واقعاً نیازی به دستکاری DOM قبل از paint ندارید، از useEffect استفاده کنید.
جدول مقایسه
useLayoutEffect | useEffect | ویژگی / معیار |
قبل از paint | بعد از Paint | زمان اجرا |
بلاککننده (Sync) | غیربلاککننده (Async) | همزمانی |
نیاز به احتیاط در استفاده | بهینهتر در اکثر سناریوها | تاثیر بر پرفورمنس |
خواندن موقعیت یا ابعاد DOM، جلوگیری از flicker | API call، عنوان صفحه، اشتراکها | مثالهای معمول |
فقط در صورت نیاز ضروری به مداخله بصری فوری | انتخاب پیشفرض در اکثر مواقع | توصیه کلی |
در پایان
در این مقاله تلاش کردیم به شکلی کامل و مرحلهبهمرحله تفاوت useEffect
و useLayoutEffect
را در ریاکت بررسی کنیم. با نگاهی به چرخهی رندر و paint شدن DOM در مرورگر، متوجه شدیم که تفاوت اصلی این دو هوک نه در ساختار یا کاربرد ظاهریشان، بلکه در زمان اجرا و تأثیرشان بر عملکرد و تجربهی کاربری است. این تفاوت زمانی، نقطهی آغاز تمایزهای مهمتری مثل مسدود یا غیرمسدود بودن رندر، نیاز یا عدم نیاز به همزمانی با layout DOM، و در نهایت تصمیمگیری در مورد استفاده از هرکدام در پروژههای واقعی است.
همچنین با مثالهای عملی، بهویژه در مورد جلوگیری از flicker یا اندازهگیری عناصر برای موقعیتدهی یا انیمیشنها، نشان دادیم که useLayoutEffect
چه زمانی واقعاً ضروری است. در کنار آن، با مرور بهترین رویهها و جدول مقایسهای، ابزار لازم برای تصمیمگیری آگاهانه در اختیار شما قرار گرفت.
در نهایت، اگر بخواهیم پیام اصلی مقاله را در یک جمله خلاصه کنیم، باید بگوییم:
تا زمانی که به دلیلی واضح به اجرای همزمان و دستکاری DOM قبل از paint نیاز ندارید، از useEffect
استفاده کنید.
با درک دقیق این دو ابزار قدرتمند، میتوانید اپلیکیشنهایی بسازید که هم روان اجرا شوند، هم بهدرستی رندر شوند و هم تجربه کاربری مطلوبی ارائه دهند — دقیقاً همان چیزی که از یک توسعهدهندهی حرفهای انتظار میرود.
همچنین برای آموزش های بیشتر ری اکت میتوانید به صفحه "مسیر آموزش ری اکت" در سایت آموزش برنامه نویسی راکت مراجعه کنید.
دیدگاه و پرسش
در حال دریافت نظرات از سرور، لطفا منتظر بمانید
در حال دریافت نظرات از سرور، لطفا منتظر بمانید