Menu
flag Ar iconالعربيةdown icon

useLayoutEffect في React: القياس قبل أن يرسم المتصفح

يعمل useLayoutEffect مثل useEffect لكنه يعمل مباشرة بعد أن تحدّث React الـ DOM وقبل أن يرسم المتصفح. استخدمه لقياس عنصر وضبط التخطيط دون وميض مرئي، واترك useEffect لكل ما عدا ذلك.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

useLayoutEffect نسخة من useEffect تعمل في وقت أبكر: مباشرة بعد أن تحدّث React الـ DOM، لكن قبل أن يرسم المتصفح الإطار الجديد. يستطيع الكود فيه قياس العناصر وتغيير التخطيط، ولا يرى المستخدم إلا النتيجة النهائية. وهو يمنع الرسم أثناء تشغيله، لذا لا تستخدمه إلا عندما تحتاج إلى هذا التوقيت.

التأثيرات معلنة بترتيب: التأثير، ثم تأثير التخطيط، لكن وحدة التحكم تطبع تأثير التخطيط أولًا في كل عرض. ترتيب الإعلان لا يهم بين الخطافين؛ بل توقيتهما.

الصيغة

التوقيع مطابق لـ useEffect: دالة إعداد، ودالة تنظيف اختيارية، ومصفوفة اعتماديات.

useLayoutEffect(() => {
    // runs after the DOM update, before paint
    return () => {
        // cleanup, before the next run and on unmount
    };
}, [dependencies]);

كل ما تعرفه عن الاعتماديات والتنظيف من useEffect ينطبق. الفرق الوحيد هو متى تستدعيه React.

متى يعمل كل منهما

بعد العرض، تحدّث React الـ DOM ثم تفعل ثلاثة أشياء بترتيب ثابت:

  1. تشغّل كل دوال تنظيف تأثيرات التخطيط وتأثيرات التخطيط، بشكل متزامن.
  2. تترك المتصفح يرسم. وإذا ضبط تأثير تخطيط حالة، تعيد React العرض أولًا، وما زال ذلك قبل الرسم.
  3. تشغّل التأثيرات العادية، عادة بعد الرسم.

عبر الشجرة، يعمل كل تأثير تخطيط قبل أي تأثير عادي، ويعمل الأبناء قبل آبائهم، لأن الأب لا يمكنه الانتهاء إلا بعد أن يكون أبناؤه في مكانهم:

تُظهر وحدة التحكم تأثيري التخطيط أولًا، Child ثم Parent، ثم التأثيرين العاديين بالترتيب نفسه. يجعل key={n} كل نقرة تركّب Parent جديدًا، فتحصل على الأسطر الأربعة نفسها في كل مرة. أضف إلى كل تأثير دالة تنظيف تسجّل، وراقب القديمة تعمل قبل الجديدة.

مشكلة الوميض

تخيّل تلميحًا ينبغي أن يظهر أعلى زره، ما لم توجد مساحة في الأعلى، فيظهر حينها أسفله. لا يمكنك معرفة ما إذا كانت هناك مساحة حتى يكون التلميح في الـ DOM وتستطيع قياس ارتفاعه. لذا يحدث العرض على مرحلتين:

  1. اعرض التلميح في مكان ما، ليمكن قياسه.
  2. قِسه، واحسب الموضع الحقيقي، واضبط الحالة، وأعد العرض.

مع useEffect قد يرسم المتصفح بين المرحلتين، فيرى المستخدم التلميح يقفز من الموضع الأول إلى الثاني. ومع useLayoutEffect تحدث المرحلة الثانية قبل الرسم، فلا يظهر الموضع الأول أبدًا.

مرّر المؤشر فوق الزر العلوي: لا توجد مساحة فوقه، فيظهر التلميح أسفله. ومرّره فوق الزر السفلي فيظهر أعلاه. وتخبرك وحدة التحكم بالجهة التي اختارها القياس. غيّر useLayoutEffect إلى useEffect ومرّر المؤشر مرة أخرى: قد يومض التلميح الآن في أعلى الصندوق لإطار واحد قبل أن يتحرك. على جهاز سريع يكون ذلك غالبًا أسرع من أن يُلاحظ، وهذا بالضبط سبب أن هذا الخطأ يُنشر ثم يُبلَّغ عنه من هواتف أبطأ.

كل من offsetHeight وoffsetTop وgetBoundingClientRect() يجبر المتصفح على حساب التخطيط، فقراءتها في تأثير التخطيط تعطيك القيم الحقيقية للـ DOM الذي كتبته React للتو.

قياس عنصر

يعمل النمط نفسه لأي حجم يعتمد عليه العرض: اقرأه بمرجع في تأثير تخطيط، وخزّنه في الحالة، واعرض به.

انقر الزر فيتحدث سطر العرض في الإطار نفسه مع النص. لا تُظهر الصفحة أبدًا نصًا جديدًا بجانب عرض قديم، لأن العرض الثاني يحدث قبل الرسم. احذف [text] من مصفوفة الاعتماديات فيعمل القياس بعد كل عرض، بما فيه العرض الذي يسببه setWidth الخاص به؛ ولا يتوقف هناك إلا لأن العرض لم يعد يتغير.

فضّل useEffect

يؤخر تأثير التخطيط الرسم حتى يُرجع، وإذا ضبط حالة، فحتى ينتهي العرض الإضافي أيضًا. والبطيء منه يجعل كل تحديث في الصفحة يبدو بطيئًا. استخدم useLayoutEffect فقط عندما تتحقق كل هذه الشروط:

  • يقرأ التأثير التخطيط (الحجم، أو الموضع، أو التمرير).
  • يغيّر ما يُعرض بناءً على تلك القراءة.
  • ستكون الحالة الوسيطة مرئية وتبدو خاطئة.

كل ما عدا ذلك (الجلب، والاشتراكات، والمؤقتات، والتسجيل، والتحليلات) ينتمي إلى useEffect. قاعدة سريعة: ابدأ بـ useEffect، ولا تنتقل إلا إذا رأيت وميضًا.

يعمل useEffect عادة بعد الرسم، لكن ليس دائمًا. عندما يأتي التحديث من فعل مستخدم منفصل مثل نقرة أو ضغطة مفتاح، قد تشغّل React التأثيرات قبل أن يرسم المتصفح. وهذا أحد أسباب ظهور الوميض في موقف دون آخر.

العرض على الخادم

لا تعمل التأثيرات أبدًا أثناء العرض على الخادم، ولا useLayoutEffect كذلك. يرسل الخادم HTML العرض الأول، ولا يعمل تأثير التخطيط إلا بعد الإماهة (hydration) في المتصفح. كانت React 18 وما قبلها تحذّر بـ "useLayoutEffect does nothing on the server" عندما يُعرض مكوّن كهذا على الخادم؛ وأسقطت React 19 هذا التحذير.

إذا كان الإطار الأول المعروض على الخادم سيبدو خاطئًا دون القياس، فهناك إصلاحان شائعان: اعرض بديلًا محايدًا (مثل إخفاء التلميح حتى يُقاس)، أو اعرض المكوّن على العميل فقط:

function ClientOnly({ children }) {
    const [mounted, setMounted] = useState(false);
    useEffect(() => setMounted(true), []);
    return mounted ? children : null;
}

useInsertionEffect

هناك خطاف تأثير ثالث، useInsertionEffect، يعمل قبل أي تأثير تخطيط. وهو موجود لمكتبات CSS-in-JS التي تولّد وسوم <style> وقت التشغيل: إدراج الأنماط هناك يعني أن تأثير التخطيط الذي يقيس عنصرًا يراه منسقًا بالفعل. لا يستطيع تحديث الحالة ولم تُربط المراجع بعد، لذا لا فائدة منه في كود التطبيقات. إن لم تكن تكتب مكتبة تنسيق، فلن تحتاج إليه.

الأسئلة الشائعة

ما الفرق بين useEffect وuseLayoutEffect؟

التوقيت. يعمل useLayoutEffect بشكل متزامن بعد أن تغيّر React الـ DOM وقبل أن يرسم المتصفح، فتُطبَّق تحديثات حالته قبل أن يرى المستخدم أي شيء. أما useEffect فيعمل عادة بعد الرسم. الواجهة ومصفوفة الاعتماديات واحدة.

متى أستخدم useLayoutEffect؟

عندما يقرأ التأثير التخطيط (حجمًا أو موضعًا) ويغيّر فورًا ما يُعرض بناءً عليه، مثل وضع تلميح أعلى زره أو أسفله. من دونه قد يرى المستخدم الموضع الأول الخاطئ لإطار واحد.

هل useLayoutEffect سيئ للأداء؟

يمنع المتصفح من الرسم حتى ينتهي، مع أي إعادة عرض يطلقها. التأثير البطيء من هذا النوع يجعل الصفحة كلها تبدو بطيئة، لذا استخدم useEffect ما لم تكن بحاجة إلى تجنب وميض مرئي.

هل يعمل useLayoutEffect على الخادم؟

لا. لا يعمل أي من التأثيرين أثناء العرض على الخادم. كانت إصدارات React الأقدم تطبع تحذيرًا عندما يُعرض مكوّن فيه useLayoutEffect على الخادم؛ ولم تعد React 19 تفعل ذلك، لكن التأثير ما زال لا يعمل إلا في المتصفح.

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن