كلا الخطّافين لهما نفس الصياغة تقريبًا — نفس الوسيطين (setup, dependencies)، ونفس فكرة دالة التنظيف (cleanup). فلماذا يوجد اثنان؟ الفرق كله يتلخّص بكلمة واحدة: التوقيت.
الفرق بالتوقيت بالضبط
| متى يعمل | يحجب رسم المتصفح؟ | |
|---|---|---|
useEffect | بعد أن يرسم المتصفح الشاشة فعليًا | لا |
useLayoutEffect | بعد تحديث الـ DOM، لكن قبل أن يرسم المتصفح أي شيء | نعم |
بمعنى آخر: React يحدّث الـ DOM دائمًا أولًا (لكلا الحالتين)، لكن مع useLayoutEffect يتوقّف قبل أن يسمح للمتصفح بطلاء (paint) هذا التحديث على الشاشة، وينتظر أن ينتهي الكود بداخله — بما فيه أي تحديث حالة يسبّبه.
المشكلة العملية: الوميض البصري (Flicker)
المثال الكلاسيكي: تلميح (Tooltip) يجب أن يظهر فوق عنصر ما، وتحتاج قياس ارتفاعه أولًا لتحديد موضعه بدقة.
// ❌ باستخدام useEffect: المستخدم يرى وميضًا سريعًا
function Tooltip() {
const ref = useRef(null);
const [top, setTop] = useState(0);
useEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTop(-height - 8);
}, []);
return <div ref={ref} style={{ position: "absolute", top }}>تلميح</div>;
}
هنا يحصل التالي: React يرسم العنصر بموضعه الأولي (top: 0) → المتصفح يطلي هذا الموضع فعليًا على الشاشة → بعدها فقط يعمل useEffect ويقيس الارتفاع ويحدّث top → رندر ثانٍ → المتصفح يطلي الموضع الصحيح. النتيجة: المستخدم يرى قفزة سريعة من الموضع الخطأ للموضع الصحيح — وميض ملحوظ خصوصًا على الأجهزة البطيئة.
// ✅ باستخدام useLayoutEffect: لا وميض إطلاقًا
function Tooltip() {
const ref = useRef(null);
const [top, setTop] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTop(-height - 8);
}, []);
return <div ref={ref} style={{ position: "absolute", top }}>تلميح</div>;
}
بنفس الكود تمامًا لكن مع useLayoutEffect: React يحدّث الـ DOM بالموضع الأولي، لكن قبل أن يسمح للمتصفح بطلاء أي شيء، يُنفَّذ الخطّاف ويقيس الارتفاع ويحدّث الحالة — فيُعاد رسم DOM بالموضع الصحيح، وعندها فقط يطلي المتصفح النتيجة النهائية دفعة واحدة. المستخدم لا يرى إلا الموضع الصحيح مباشرة.
متى تستخدم كل واحد؟
- استخدم
useEffectكافتراضي دائمًا — جلب بيانات، اشتراكات، مؤقّتات، أي تأثير جانبي لا يعتمد على قياس بصري فوري. لا يحجب رسم الصفحة، فتجربة المستخدم أسرع. - انتقل لـ
useLayoutEffectفقط عندما ترى وميضًا فعليًا ناتجًا عن قياس DOM (أبعاد، موضع، تمرير scroll) وتحديث حالة بناءً عليه قبل أن يرى المستخدم أي شيء.
💡 التوثيق الرسمي لـ React يفتح صفحة
useLayoutEffectبتحذير صريح: يفضَّلuseEffectكلما أمكن.useLayoutEffectأداة لحالة ضيّقة ومحدّدة، لا بديل عام لـuseEffect.
قيد إضافي: لا يعمل على الخادم
useLayoutEffect لا يُنفَّذ إطلاقًا أثناء التصيير من الخادم (SSR) — لا توجد معلومات تخطيط (Layout) أصلًا هناك. إن كان مكوّنك يُرسَم على الخادم وتحتاج هذا النمط، الحلول الشائعة: تأجيل قياس الـ Layout إلى بعد التركيب في المتصفح، أو تعليم المكوّن كأنه يعمل في المتصفح فقط.
خلاصة
الاثنان يشتركان بنفس الصياغة، والفرق الوحيد الحقيقي هو التوقيت: useEffect يعمل بعد الرسم فلا يحجب شيئًا، وuseLayoutEffect يعمل قبل الرسم فيحجب المتصفح حتى ينتهي. ابدأ دائمًا بـ useEffect، وانتقل لـ useLayoutEffect فقط عندما تحل مشكلة وميض فعلية ترها بعينك.
اقرأ الشرح الكامل في درس useEffect ومرجع useLayoutEffect من مسار React بالعربي، وإن كنت تواجه حلقة تحديثات لا تتوقف بدل الوميض، راجع حل حلقة useEffect اللانهائية.