تريد React أن تكون مكوناتك قابلة للتنبؤ. إذا أعطيتها نفس الحالة (state) والخصائص (props)، فيجب أن ترسم نفس واجهة المستخدم في كل مرة. لكن معظم التطبيقات الواقعية لا يمكنها البقاء داخل تلك الفقاعة؛ فهي تحتاج إلى التواصل مع العالم الخارجي. لوحة التحكم تحتاج إلى أرقام محدثة من خادم، وأداة الدردشة تحتاج إلى الاستماع للرسائل، والمؤقت يحتاج إلى العمل. هذه العمليات هي "آثار جانبية" (side effects)، وهي تقع خارج دورة الرندرة (render cycle) الخاصة بـ React. يُعد الخطاف useEffect هو المكان الذي تضع فيه هذا العمل الفوضوي وغير المتوقع ليبقى مكونك نفسه دقيقًا ومنضبطًا.

الآثار الجانبية: ما الذي ينتمي إلى داخل useEffect

الأثر الجانبي هو أي شيء يلمس العالم خارج نطاق إرجاع JSX. يجب أن تكون مرحلة الرندرة (render phase) في React نقية (pure). عندما تبدأ في جلب البيانات، أو الكتابة في متغيرات عامة، أو إضافة مستمعي أحداث (listeners) إلى الـ DOM، فأنت بذلك قد خرجت من النطاق النقي.

تشمل الأمثلة الشائعة ما يلي:

  • جلب البيانات من API
  • إعداد المؤقتات (timers) أو الفترات الزمنية (intervals)
  • إضافة مستمعي أحداث (event listeners) إلى window أو document
  • تحديث عنوان علامة تبويب المتصفح
  • الاتصال بـ WebSockets

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

كيف تتحكم مصفوفة التبعيات في التوقيت

الوسيط الثاني لـ useEffect هو مصفوفة التبعيات (dependency array)، وهي أكبر مصدر للارتباك للمطورين المنتقلين من المكونات الفئوية (class components). فكر فيها كمجموعة من المتغيرات التي تراقبها React لتقرر ما إذا كانت ستتخطى الأثر الخاص بك أو تنفذه بعد عملية الرندرة الحالية.

هناك ثلاثة أنماط ستستخدمها بشكل متكرر:

عدم وجود مصفوفة تبعيات على الإطلاق. إذا حذفت المصفوفة تمامًا، تفترض React أنك تريد تشغيل الأثر بعد كل عملية رندرة، بما في ذلك المرة الأولى. وهذا نادرًا ما يكون هو المطلوب. إذا كان الأثر الخاص بك يقوم بطلب شبكة أو عملية DOM ثقيلة، فإن تشغيله مع كل ضغطة مفتاح أو تغيير في الحالة سيؤدي إلى تدهور الأداء بشكل كبير. استخدم هذا النمط فقط عندما تحتاج حقًا إلى إعادة تشغيل شيء ما لأن أي خاصية (prop) أو حالة (state) قد تكون تغيرت ولا يمكنك تحديد أي منها.

مصفوفة فارغة []. هذا يخبر React بتشغيل الأثر مرة واحدة فقط، مباشرة بعد تثبيت (mount) المكون وجاهزية الـ DOM. هذا هو المكان المناسب لجلب البيانات الأولية. على سبيل المثال، إذا كان مكونك يحمل بيانات ملف تعريف المستخدم، فأنت تريد أن يتم هذا الطلب مرة واحدة فقط عند ظهور صفحة الملف الشخصي، وليس في كل مرة يتفاعل فيها المستخدم مع نموذج في أسفل الصفحة.

مصفوفة تحتوي على متغيرات محددة [count]. هذه هي أداة الدقة. تقارن React القيم الحالية لهذه التبعيات بقيمها أثناء آخر عملية رندرة. إذا تغير أي منها، يتم تشغيل الأثر. إذا لم يتغير شيء في القائمة، تتخطى React الأثر تمامًا.

إذا كنت تقوم بمزامنة عنوان علامة تبويب المتصفح مع متغير حالة، فستضع ذلك المتغير في مصفوفة التبعيات. ستقوم React بعد ذلك بتحديث العنوان فقط عندما تتغير تلك القيمة. إذا تركته خارج المصفوفة، سيبقى العنوان قديمًا. وإذا وضعت متغيرات حالة غير ذات صلة، فستستهلك دورات المعالجة في تحديث العنوان لتغييرات لا تهم.

التنظيف ليس خيارًا

تترك بعض الآثار خلفها آثارًا (footprints). فالمؤقت يستمر في العد، ومستمع الأحداث يستمر في العمل، وWebSocket يظل مفتوحًا. عندما يتم إلغاء تثبيت (unmount) مكونك، أو حتى عندما يُعاد تشغيل أثر ما لأن تبعياته قد تغيرت، فإن React لا تقوم تلقائيًا بتنظيف بقايا الأثر السابق. هذه مهمتك أنت.

يمكنك إنشاء دالة تنظيف عن طريق إرجاع دالة من داخل useEffect. تقوم React باستدعاء دالة التنظيف هذه قبل تطبيق الأثر التالي، ومرة أخرى عندما يغادر المكون الشاشة.

يجب عليك استخدام التنظيف من أجل:

  • مسح الفترات أو المهلة باستخدام clearInterval أو clearTimeout
  • إزالة مستمعي الأحداث المضافين إلى window أو document أو العقد الخارجية
  • إلغاء الاشتراك من تدفقات البيانات أو الخدمات

أهمل هذا، وستواجه تسريبات في الذاكرة (memory leaks). يتم تثبيت مكون، ويضيف مستمع تمرير (scroll listener)، ثم يتم إلغاء تثبيته، ولكن يظل المستمع يعمل. يحتفظ المتصفح بالدالة المستدعاة (callback) وعقد الـ DOM التي تشير إليها. بمرور الوقت، خاصة في تطبيقات الصفحة الواحدة (SPA) ذات التنقل الكثيف، تتراكم هذه "الأشباح" وتؤدي إلى إبطاء علامة التبويب. الحل عادة ما يكون مجرد بضعة أسطر: أرجع دالة تقوم بإزالة ما أضفته.

أخطاء شائعة تصل إلى مرحلة الإنتاج

حتى المطورون ذوو الخبرة يلجؤون إلى useEffect عندما يكون هناك خيار أبسط. إليك ثلاثة أنماط يجب أن تثير علامة تحذير أثناء مراجعة الكود.

الحلقات اللانهائية. لا تقم أبدًا بتحديث متغير state داخل useEffect إذا كان نفس المتغير موجودًا في مصفوفة التبعيات (dependency array) الخاصة بك، ما لم يكن لديك شرط تحكم (gate condition) يكسر هذه الحلقة. إذا قمت بقراءة count وزيادته، وأدرجت count كـ dependency، فسيلاحظ React التغيير، ويعيد عملية الـ re-render، ثم يشغل الـ effect مرة أخرى، ويزيد القيمة مجددًا، مما يؤدي إلى تجميد المتصفح.

تأثيرات (effects) غير ضرورية. لا تستخدم useEffect لحساب قيمة من props أو state موجودة. إذا كان بإمكانك اشتقاقها مباشرة أثناء عملية الـ render، فافعل ذلك ببساطة. القيم المشتقة مكانها داخل جسم المكون (component body) أو ضمن عملية حسابية مخزنة (memoized calculation) باستخدام useMemo. نقلها إلى effect يشتت منطق الكود الخاص بك بين مرحلتي الـ render والـ effect دون أي فائدة، ويجعل الكود أصعب في التتبع.

الأداة الخاطئة لإجراءات المستخدم. use