قد لا تحتاج إلى Effect
تعتبر Effects منفذ هروب من نموذج React. فهي تتيح لك "الخروج" من React ومزامنة مكوناتك مع نظام خارجي ما، مثل عنصر واجهة مستخدم غير React، أو الشبكة، أو DOM المتصفح. إذا لم يكن هناك نظام خارجي متضمن (على سبيل المثال، إذا كنت تريد تحديث حالة مكون عند تغيير بعض الخصائص props أو الحالة state)، فلا يجب أن تحتاج إلى Effect. إزالة Effects غير الضرورية سيجعل كودك أسهل في المتابعة، وأسرع في التشغيل، وأقل عرضة للأخطاء.
سوف تتعلم
- لماذا وكيفية إزالة Effects غير الضرورية من مكوناتك
- كيفية تخزين العمليات الحسابية المكلفة مؤقتًا بدون Effects
- كيفية إعادة تعيين وضبط حالة المكون بدون Effects
- كيفية مشاركة المنطق بين معالجات الأحداث
- أي منطق يجب نقله إلى معالجات الأحداث
- كيفية إخطار المكونات الأصلية بالتغييرات
كيفية إزالة Effects غير الضرورية
هناك حالتان شائعتان لا تحتاج فيهما إلى Effects:
- لا تحتاج إلى Effects لتحويل البيانات من أجل العرض.على سبيل المثال، لنفترض أنك تريد تصفية قائمة قبل عرضها. قد تشعر بالرغبة في كتابة Effect يقوم بتحديث متغير حالة عندما تتغير القائمة. ومع ذلك، هذا غير فعال. عندما تقوم بتحديث الحالة، سيقوم React أولاً باستدعاء دوال مكوناتك لحساب ما يجب أن يكون على الشاشة. ثم سيقوم React بـ"إيداع"هذه التغييرات في DOM، مما يؤدي إلى تحديث الشاشة. ثم سيقوم React بتشغيل Effects الخاصة بك. إذا كان Effect الخاص بكأيضًايقوم بتحديث الحالة على الفور، فهذا يعيد العملية بأكملها من البداية! لتجنب عمليات التصيير غير الضرورية، قم بتحويل جميع البيانات في المستوى الأعلى لمكوناتك. سيتم إعادة تشغيل هذا الكود تلقائيًا كلما تغيرت الخصائص props أو الحالة state الخاصة بك.
- لا تحتاج إلى Effects للتعامل مع أحداث المستخدم.على سبيل المثال، لنفترض أنك تريد إرسال طلب POST إلى
/api/buyوعرض إشعار عندما يشتري المستخدم منتجًا. في معالج حدث النقر على زر الشراء، تعرف بالضبط ما حدث. بحلول وقت تشغيل Effect، لا تعرفماذافعل المستخدم (على سبيل المثال، أي زر تم النقر عليه). لهذا السبب عادةً ما تتعامل مع أحداث المستخدم في معالجات الأحداث المقابلة.
أنتتحتاجإلى Effects من أجلالمزامنةمع الأنظمة الخارجية. على سبيل المثال، يمكنك كتابة Effect يحافظ على مزامنة عنصر واجهة jQuery مع حالة React. يمكنك أيضًا جلب البيانات باستخدام Effects: على سبيل المثال، يمكنك مزامنة نتائج البحث مع استعلام البحث الحالي. تذكر أنأطر العمل الحديثةتوفر آليات مدمجة لجلب البيانات أكثر كفاءة من كتابة Effects مباشرة في مكوناتك.
لمساعدتك على اكتساب الحدس الصحيح، دعنا نلقي نظرة على بعض الأمثلة الشائعة الملموسة!
تحديث الحالة بناءً على الخصائص props أو الحالة state
لنفترض أن لديك مكونًا يحتوي على متغيري حالة:firstName و lastName. وتريد حسابfullNameمنهما عن طريق دمجهما. علاوة على ذلك، تريد أن يتم تحديثfullNameكلما تغيرfirstNameأوlastName. قد تكون فكرتك الأولى هي إضافة متغير حالةfullNameوتحديثه في Effect:
هذا أكثر تعقيدًا مما هو ضروري. وهو أيضًا غير فعال: فهو يقوم بتمرير عرض كامل بقيمة قديمة لـfullName، ثم يعيد العرض فورًا بالقيمة المحدثة. قم بإزالة متغير الحالة و Effect:
عندما يمكن حساب شيء من الخصائص أو الحالة الموجودة،لا تضعه في الحالة.بدلاً من ذلك، احسبه أثناء العرض.هذا يجعل كودك أسرع (تتجنب التحديثات الإضافية "المتتالية")، وأبسط (تزيل بعض الكود)، وأقل عرضة للأخطاء (تتجنب الأخطاء الناتجة عن عدم تزامن متغيرات الحالة المختلفة مع بعضها). إذا كان هذا النهج جديدًا بالنسبة لك،التفكير في Reactيشرح ما يجب وضعه في الحالة.
التخزين المؤقت للحسابات المكلفة
يحسب هذا المكونvisibleTodosعن طريق أخذtodosالتي يستقبلها عبر الخصائص وتصفيتها وفقًا لخاصيةfilter. قد تشعر بالرغبة في تخزين النتيجة في الحالة وتحديثها من Effect:
كما في المثال السابق، هذا غير ضروري وغير فعال. أولاً، قم بإزالة الحالة و Effect:
عادةً، هذا الرمز جيد! ولكن ربما تكونgetFilteredTodos()بطيئة أو لديك الكثير منtodos. في هذه الحالة، لا تريد إعادة حسابgetFilteredTodos()إذا تغير متغير حالة غير ذي صلة مثلnewTodo.
يمكنك تخزين (أو"تخزين مؤقت") عملية حسابية مكلفة عن طريق تغليفها في خطافuseMemo:
ملاحظة
مترجم Reactيمكنه تخزين العمليات الحسابية المكلفة تلقائيًا لك، مما يلغي الحاجة إلىuseMemoيدوي في كثير من الحالات.
أو، مكتوبة كسطر واحد:
هذا يخبر React أنك لا تريد إعادة تشغيل الدالة الداخلية إلا إذا تغيرت إماtodosأوfilter.سيتذكر React القيمة المرجعة لـgetFilteredTodos()أثناء التصيير الأولي. خلال التصييرات التالية، سيتحقق مما إذا كانتtodosأوfilterمختلفة. إذا كانت كما هي في المرة السابقة، فإنuseMemoسترجع النتيجة الأخيرة التي قامت بتخزينها. ولكن إذا كانت مختلفة، فإن React ستقوم باستدعاء الدالة الداخلية مرة أخرى (وتخزين نتيجتها).
الدالة التي تقوم بتغليفها فيuseMemoتعمل أثناء التصيير، لذلك هذا يعمل فقط معالحسابات النقية.
إعادة تعيين جميع الحالات عند تغيير خاصية (prop)
يستقبل مكونProfilePage خاصية userId. تحتوي الصفحة على حقل إدخال للتعليقات، وتستخدم متغير حالةcommentلحفظ قيمته. في أحد الأيام، تلاحظ مشكلة: عند التنقل من ملف تعريف إلى آخر، لا تتم إعادة تعيين حالةcomment. نتيجة لذلك، من السهل نشر تعليق عن طريق الخطأ على ملف تعريف مستخدم خاطئ. لإصلاح المشكلة، تريد مسح متغير الحالةcommentفي كل مرة تتغير فيها خاصيةuserId:
هذا غير فعال لأنProfilePageومكوناتها الفرعية سوف تُصيَّر أولاً بالقيمة القديمة، ثم تُصيَّر مرة أخرى. كما أنه معقد لأنك ستحتاج إلى فعل هذا فيكلمكون لديه بعض الحالة داخلProfilePage. على سبيل المثال، إذا كانت واجهة المستخدم للتعليقات متداخلة، سترغب في مسح حالة التعليقات المتداخلة أيضًا.
بدلاً من ذلك، يمكنك إخبار React أن ملف تعريف كل مستخدم هو مفهوميًا ملف تعريفمختلفمن خلال إعطائه مفتاحًا صريحًا. قسّم مكونك إلى جزأين ومرِّر سمةkeyمن المكون الخارجي إلى المكون الداخلي:
عادةً، يحافظ React على الحالة عندما يُصيَّر نفس المكون في نفس المكان.بتمريرuserIdكمفتاحkeyإلى مكونProfile، أنت تطلب من React أن تعامل مكونيProfileمختلفين بقيمuserIdمختلفة كمكونين مختلفين لا يجب أن يتشاركا أي حالة.كلما تغير المفتاح (الذي عينته إلىuserId)، سيعيد React إنشاء DOM وإعادة تعيين الحالةلمكونProfileوجميع مكوناته الفرعية. الآن سيتم مسح حقلcommentتلقائيًا عند التنقل بين الملفات الشخصية.
لاحظ في هذا المثال، أن المكون الخارجيProfilePageفقط هو المُصدَّر والمرئي للملفات الأخرى في المشروع. المكونات التي تُصيَّرProfilePageلا تحتاج إلى تمرير المفتاح إليه: إنها تمررuserIdكخاصية عادية. حقيقة أنProfilePageيمررها كمفتاحkeyإلى المكون الداخليProfileهي تفصيل تنفيذي.
ضبط بعض الحالة عند تغير خاصية
في بعض الأحيان، قد ترغب في إعادة تعيين أو ضبط جزء من الحالة عند تغير خاصية، وليس كلها.
يتلقى مكونList قائمة من itemsكخاصية، ويحافظ على العنصر المحدد في متغير الحالةselection. تريد إعادة تعيينselectionإلىnullكلما تلقت خاصيةitemsمصفوفة مختلفة:
هذا أيضًا ليس مثاليًا. في كل مرة تتغيرitems، سيتم عرضListومكوناته الفرعية بقيمةselectionقديمة في البداية. ثم سيقوم React بتحديث DOM وتشغيل المؤثرات. أخيرًا، سيؤدي استدعاءsetSelection(null)إلى إعادة عرض أخرى لـListومكوناته الفرعية، مما يعيد بدء هذه العملية بأكملها مرة أخرى.
ابدأ بحذف المؤثر. بدلاً من ذلك، اضبط الحالة مباشرة أثناء العرض:
تخزين المعلومات من العروض السابقةمثل هذا قد يكون صعب الفهم، ولكنه أفضل من تحديث نفس الحالة في مؤثر. في المثال أعلاه، يتم استدعاءsetSelectionمباشرة أثناء العرض. سيعيد React عرضListفورًابعد خروجه بعبارةreturn. لم يقم React بعد بعرض أبناءListأو تحديث DOM، لذا هذا يسمح لأبناءListبتخطي عرض قيمةselectionالقديمة.
عندما تقوم بتحديث مكون أثناء العرض، يتجاهل React JSX المُعاد ويحاول العرض مرة أخرى على الفور. لتجنب عمليات إعادة المحاولة المتتالية البطيئة جدًا، يسمح React لك فقط بتحديث حالة المكوننفسهأثناء العرض. إذا قمت بتحديث حالة مكون آخر أثناء العرض، فسترى خطأ. شرط مثلitems !== prevItemsضروري لتجنب الحلقات. يمكنك ضبط الحالة بهذه الطريقة، ولكن أي تأثيرات جانبية أخرى (مثل تغيير DOM أو ضبط المؤقتات) يجب أن تبقى في معالجات الأحداث أو المؤثرات من أجلالحفاظ على نقاء المكونات.
على الرغم من أن هذا النمط أكثر كفاءة من Effect، إلا أن معظم المكونات لا تحتاج إليه أيضًا.بغض النظر عن كيفية قيامك بذلك، فإن ضبط الحالة بناءً على الخصائص أو الحالة الأخرى يجعل تدفق بياناتك أكثر صعوبة في الفهم والتصحيح. تحقق دائمًا مما إذا كان بإمكانكإعادة تعيين كل الحالة باستخدام مفتاحأوحساب كل شيء أثناء التصييربدلاً من ذلك. على سبيل المثال، بدلاً من تخزين (وإعادة تعيين)العنصرالمحدد، يمكنك تخزينمعرف العنصر المحدد:
لم يعد هناك حاجة إلى "ضبط" الحالة على الإطلاق. إذا كان العنصر ذو المعرف المحدد موجودًا في القائمة، يظل محددًا. إذا لم يكن موجودًا، فإنselectionالمحسوب أثناء التصيير سيكونnullلأنه لم يتم العثور على عنصر مطابق. هذا السلوك مختلف، ولكنه يمكن القول إنه أفضل لأن معظم التغييرات فيitemsتحافظ على التحديد.
مشاركة المنطق بين معالجات الأحداث
لنفترض أن لديك صفحة منتج بها زرين (شراء ودفع) يسمحان لك بشراء هذا المنتج. تريد عرض إشعار في كل مرة يضع فيها المستخدم المنتج في سلة التسوق. استدعاءshowNotification()في معالجات النقر لكلا الزرين يبدو مكررًا، لذا قد تميل إلى وضع هذا المنطق في Effect:
هذا Effect غير ضروري. كما أنه سيسبب على الأرجح أخطاء. على سبيل المثال، لنفترض أن تطبيقك "يتذكر" سلة التسوق بين إعادة تحميل الصفحة. إذا أضفت منتجًا إلى السلة مرة واحدة وقمت بتحديث الصفحة، فسيظهر الإشعار مرة أخرى. سيستمر في الظهور في كل مرة تقوم فيها بتحديث صفحة ذلك المنتج. هذا لأنproduct.isInCartسيكون بالفعلtrueعند تحميل الصفحة، لذا فإن Effect أعلاه سيدعوshowNotification().
عندما لا تكون متأكدًا مما إذا كان يجب أن يكون بعض الكود في Effect أو في معالج حدث، اسأل نفسكلماذايحتاج هذا الكود إلى التشغيل. استخدم Effects فقط للكود الذي يجب أن يعمللأنالمكون تم عرضه للمستخدم.في هذا المثال، يجب أن يظهر الإشعار لأن المستخدمضغط على الزر، وليس لأن الصفحة تم عرضها! احذف Effect وضع المنطق المشترك في دالة يتم استدعاؤها من كلا معالجي الأحداث:
هذا يزيل Effect غير الضروري ويصلح الخطأ.
إرسال طلب POST
مكونFormهذا يرسل نوعين من طلبات POST. يرسل حدث تحليلي عندما يتم تركيبه. عندما تملأ النموذج وتضغط على زر الإرسال، سيرسل طلب POST إلى نقطة النهاية/api/register:
لنطبق نفس المعايير كما في المثال السابق.
يجب أن يبقى طلب POST الخاص بالتحليلات في Effect. وذلك لأنالسببلإرسال حدث التحليلات هو أن النموذج تم عرضه. (سيتم تشغيله مرتين في بيئة التطوير، ولكنانظر هناللتعامل مع ذلك.)
ومع ذلك، فإن طلب POST إلى/api/register ليس ناتجًا عن عرضالنموذج. أنت تريد فقط إرسال الطلب في لحظة محددة: عندما يضغط المستخدم على الزر. يجب أن يحدث فقطخلال ذلك التفاعل المحدد. احذف Effect الثاني وانقل طلب POST هذا إلى معالج الحدث:
عندما تختار ما إذا كنت ستضع بعض المنطق في معالج حدث أو في Effect، فإن السؤال الرئيسي الذي تحتاج للإجابة عليه هوما نوع المنطقمن وجهة نظر المستخدم. إذا كان هذا المنطق ناتجًا عن تفاعل معين، فاحتفظ به في معالج الحدث. إذا كان ناتجًا عنرؤيةالمستخدم للمكون على الشاشة، فاحتفظ به في Effect.
سلاسل الحسابات
في بعض الأحيان قد تشعر بالرغبة في ربط Effects التي يقوم كل منها بتعديل جزء من الحالة بناءً على حالة أخرى:
هناك مشكلتان في هذا الرمز.
المشكلة الأولى هي أنه غير فعال للغاية: يجب أن يعيد المكون (ومكوناته الفرعية) التصيير بين كل استدعاءsetفي السلسلة. في المثال أعلاه، في أسوأ الحالات (setCard→ تصيير →setGoldCardCount→ تصيير →setRound→ تصيير →setIsGameOver→ تصيير) هناك ثلاث عمليات تصيير غير ضرورية للشجرة أدناه.
المشكلة الثانية هي أنه حتى لو لم يكن بطيئًا، مع تطور الكود الخاص بك، ستواجه حالات لا تناسب فيها "السلسلة" التي كتبتها المتطلبات الجديدة. تخيل أنك تضيف طريقة للتنقل عبر سجل حركات اللعبة. ستفعل ذلك عن طريق تحديث كل متغير حالة إلى قيمة من الماضي. ومع ذلك، فإن تعيين حالةcardإلى قيمة من الماضي سيؤدي إلى تشغيل سلسلة التأثيرات مرة أخرى ويغير البيانات التي تعرضها. مثل هذا الكود غالبًا ما يكون صلبًا وهشًا.
في هذه الحالة، من الأفضل حساب ما يمكنك حسابه أثناء التصيير، وضبط الحالة في معالج الأحداث:
هذا أكثر كفاءة بكثير. أيضًا، إذا قمت بتنفيذ طريقة لعرض سجل اللعبة، ستتمكن الآن من تعيين كل متغير حالة إلى حركة من الماضي دون تشغيل سلسلة التأثيرات التي تضبط كل قيمة أخرى. إذا كنت بحاجة إلى إعادة استخدام المنطق بين عدة معالجات للأحداث، يمكنكاستخراج دالةواستدعائها من تلك المعالجات.
تذكر أنه داخل معالجات الأحداث،تتصرف الحالة مثل لقطة.على سبيل المثال، حتى بعد استدعاءsetRound(round + 1)، سيعكس متغيرroundالقيمة في الوقت الذي نقر فيه المستخدم على الزر. إذا كنت بحاجة إلى استخدام القيمة التالية للحسابات، فقم بتعريفها يدويًا مثلconst nextRound = round + 1.
في بعض الحالات،لا يمكنكحساب الحالة التالية مباشرة في معالج الأحداث. على سبيل المثال، تخيل نموذجًا يحتوي على قوائم منسدلة متعددة حيث تعتمد خيارات القائمة المنسدلة التالية على القيمة المحددة للقائمة المنسدلة السابقة. عندها، تكون سلسلة من التأثيرات مناسبة لأنك تقوم بالمزامنة مع الشبكة.
تهيئة التطبيق
يجب أن يتم تنفيذ بعض المنطق مرة واحدة فقط عند تحميل التطبيق.
قد تميل إلى وضعه في تأثير في المكون الأعلى مستوى:
ومع ذلك، ستكتشف سريعًا أنهيتم تشغيله مرتين في بيئة التطوير.يمكن أن يسبب هذا مشكلات — على سبيل المثال، ربما يجعل رمز المصادقة غير صالح لأن الدالة لم تُصمم ليتم استدعاؤها مرتين. بشكل عام، يجب أن تكون مكوناتك قادرة على التحمل عند إعادة التركيب. وهذا يشمل مكونك الأعلى مستوىApp.
على الرغم من أنه قد لا يتم إعادة تركيبه عمليًا في الإنتاج، فإن اتباع نفس القيود في جميع المكونات يجعل من السهل نقل الكود وإعادة استخدامه. إذا كان يجب تنفيذ بعض المنطقمرة واحدة لكل تحميل تطبيقبدلاً منمرة واحدة لكل تركيب مكون، أضف متغيرًا أعلى مستوى لتتبع ما إذا كان قد تم تنفيذه بالفعل:
يمكنك أيضًا تشغيله أثناء تهيئة الوحدة النمطية وقبل تصيير التطبيق:
يتم تشغيل الكود في المستوى الأعلى مرة واحدة عند استيراد المكون الخاص بك — حتى لو لم ينتهي الأمر إلى تصييره. لتجنب التباطؤ أو السلوك المفاجئ عند استيراد مكونات عشوائية، لا تفرط في استخدام هذا النمط. احتفظ بمنطق التهيئة على مستوى التطبيق لوحدات المكونات الجذرية مثلApp.jsأو في نقطة الدخول لتطبيقك.
إخطار المكونات الأصلية حول تغييرات الحالة
لنفترض أنك تكتب مكونToggleبحالة داخليةisOnوالتي يمكن أن تكون إماtrueأوfalse. هناك عدة طرق مختلفة لتبديله (بالنقر أو السحب). تريد إخطار المكون الأصلي كلما تغيرت الحالة الداخلية لـToggle، لذا تعرض حدثonChangeوتستدعيه من تأثير:
كما في السابق، هذا ليس مثاليًا. يقومToggleبتحديث حالته أولاً، ثم يقوم React بتحديث الشاشة. بعد ذلك يقوم React بتشغيل Effect، والذي يستدعي دالةonChangeالممررة من المكون الأب. الآن سيقوم المكون الأب بتحديث حالته الخاصة، مما يبدأ جولة تصيير أخرى. سيكون من الأفضل القيام بكل شيء في جولة واحدة.
احذف Effect وقم بدلاً من ذلك بتحديث حالةكلاالمكونين ضمن نفس معالج الحدث:
بهذا النهج، يقوم كل من مكونToggleوالمكون الأب بتحديث حالتهما أثناء الحدث. يقوم Reactبتجميع التحديثاتمن المكونات المختلفة معًا، لذلك ستكون هناك جولة تصيير واحدة فقط.
قد تتمكن أيضًا من إزالة الحالة تمامًا، واستقبالisOnمن المكون الأب بدلاً من ذلك:
"رفع الحالة للأعلى"يتيح للمكون الأب التحكم الكامل فيToggleعن طريق تبديل حالته الخاصة. هذا يعني أن المكون الأب سيحتوي على منطق أكثر، ولكن ستكون هناك حالة أقل بشكل عام للقلق بشأنها. كلما حاولت الحفاظ على متغيرين مختلفين للحالة متزامنين، حاول رفع الحالة للأعلى بدلاً من ذلك!
تمرير البيانات إلى المكون الأب
يقوم مكونChildهذا بجلب بعض البيانات ثم يمررها إلى مكونParentفي Effect:
في React، تتدفق البيانات من المكونات الأصل إلى المكونات الفرعية التابعة لها. عندما ترى شيئًا خاطئًا على الشاشة، يمكنك تتبع مصدر المعلومات عن طريق الصعود في سلسلة المكونات حتى تجد المكون الذي يمرر الخاصية (prop) الخاطئة أو لديه الحالة (state) الخاطئة. عندما تقوم المكونات الفرعية بتحديث حالة مكوناتها الأصل في Effects، يصبح تدفق البيانات صعب التتبع للغاية. نظرًا لأن كلًا من المكون الفرعي والأصل يحتاج إلى نفس البيانات، دع المكون الأصل يجلب تلك البيانات، ويمررها للأسفلإلى المكون الفرعي بدلاً من ذلك:
هذا أبسط ويبقي تدفق البيانات متوقعًا: البيانات تتدفق من الأعلى (الأصل) إلى الأسفل (الفرعي).
الاشتراك في مخزن بيانات خارجي
في بعض الأحيان، قد تحتاج مكوناتك إلى الاشتراك في بعض البيانات خارج حالة React. قد تكون هذه البيانات من مكتبة طرف ثالث أو من واجهة برمجة تطبيقات (API) مدمجة في المتصفح. نظرًا لأن هذه البيانات يمكن أن تتغير دون علم React، تحتاج إلى الاشتراك يدويًا لمكوناتك فيها. غالبًا ما يتم ذلك باستخدام Effect، على سبيل المثال:
هنا، يشترك المكون في مخزن بيانات خارجي (في هذه الحالة، واجهة برمجة تطبيقات المتصفحnavigator.onLine). نظرًا لأن هذه الواجهة غير موجودة على الخادم (لذا لا يمكن استخدامها للـ HTML الأولي)، يتم تعيين الحالة في البداية إلىtrue. كلما تغيرت قيمة مخزن البيانات هذا في المتصفح، يقوم المكون بتحديث حالته.
على الرغم من أن استخدام Effects لهذا الغرض شائع، إلا أن React لديها خطاف (Hook) مخصص للاشتراك في مخزن بيانات خارجي يُفضل استخدامه بدلاً من ذلك. احذف الـ Effect واستبدله باستدعاء لـuseSyncExternalStore:
هذا النهج أقل عرضة للأخطاء من المزامنة اليدوية للبيانات القابلة للتغيير مع حالة React باستخدام Effect. عادةً، ستكتب خطافًا مخصصًا مثلuseOnlineStatus()أعلاه حتى لا تضطر إلى تكرار هذا الرمز في المكونات الفردية.اقرأ المزيد عن الاشتراك في مخازن البيانات الخارجية من مكونات React.
جلب البيانات
تستخدم العديد من التطبيقات Effects لبدء جلب البيانات. من الشائع جدًا كتابة Effect لجلب البيانات مثل هذا:
لا تحتاج إلى نقل عملية الجلب هذه إلى معالج حدث.
قد يبدو هذا متناقضًا مع الأمثلة السابقة حيث كنت بحاجة إلى وضع المنطق في معالجات الأحداث! ومع ذلك، ضع في اعتبارك أنحدث الكتابةليس هو السبب الرئيسي للجلب. غالبًا ما يتم تعبئة مدخلات البحث مسبقًا من عنوان URL، وقد يقوم المستخدم بالتنقل للخلف والأمام دون لمس المدخل.
لا يهم من أين يأتيpage و query. طالما أن هذا المكون مرئيًا، تريد الحفاظ علىresultsمتزامنةمع البيانات من الشبكة للـpage و queryالحاليين. لهذا السبب هو Effect.
ومع ذلك، فإن الكود أعلاه به خطأ. تخيل أنك تكتب"hello"بسرعة. عندها سيتغيرquery من "h"، إلى"he"،"hel"،"hell"، و"hello". سيؤدي هذا إلى بدء عمليات جلب منفصلة، ولكن لا يوجد ضمان بشأن الترتيب الذي ستصل به الردود. على سبيل المثال، قد يصل رد"hell" بعدرد"hello". نظرًا لأنه سيستدعيsetResults()أخيرًا، ستقوم بعرض نتائج البحث الخاطئة. وهذا ما يسمى"حالة سباق": طلبتان مختلفتان "تسابقا" ضد بعضهما البعض ووصلتا بترتيب مختلف عما توقعته.
لإصلاح حالة السباق، تحتاج إلىإضافة دالة تنظيفلتجاهل الردود القديمة:
يضمن هذا أنه عندما يقوم التأثير الخاص بك بجلب البيانات، سيتم تجاهل جميع الاستجابات باستثناء آخر طلب تم إرساله.
معالجة ظروف السباق ليست الصعوبة الوحيدة في تنفيذ جلب البيانات. قد ترغب أيضًا في التفكير في تخزين الاستجابات مؤقتًا (بحيث يمكن للمستخدم النقر على زر الرجوع ورؤية الشاشة السابقة على الفور)، وكيفية جلب البيانات على الخادم (بحيث يحتوي HTML الذي تم تقديمه من الخادم في البداية على المحتوى الذي تم جلبها بدلاً من مؤشر التحميل)، وكيفية تجنب شلالات الشبكة (بحيث يمكن للمكون الفرعي جلب البيانات دون انتظار كل مكون أب).
تنطبق هذه القضايا على أي مكتبة واجهة مستخدم، وليس فقط React. حلها ليس بالأمر البسيط، ولهذا توفرالأطر الحديثةآليات جلب بيانات مدمجة أكثر كفاءة من جلب البيانات في التأثيرات.
إذا كنت لا تستخدم إطار عمل (ولا ترغب في بناء إطارك الخاص) ولكنك ترغب في جعل جلب البيانات من التأثيرات أكثر ملاءمة، ففكر في استخراج منطق الجلب الخاص بك إلى خطاف مخصص كما في هذا المثال:
من المحتمل أنك ترغب أيضًا في إضافة بعض المنطق لمعالجة الأخطاء وتتبع ما إذا كان المحتوى قيد التحميل. يمكنك بناء خطاف مثل هذا بنفسك أو استخدام أحد الحلول العديدة المتاحة بالفعل في نظام React البيئي.على الرغم من أن هذا وحده لن يكون بنفس كفاءة استخدام آلية جلب البيانات المدمجة في إطار عمل، فإن نقل منطق جلب البيانات إلى خطاف مخصص سيجعل من الأسهل تبني استراتيجية فعالة لجلب البيانات لاحقًا.
بشكل عام، كلما اضطررت إلى اللجوء لكتابة التأثيرات، راقب متى يمكنك استخراج جزء من الوظيفة إلى خطاف مخصص بواجهة برمجة تطبيقات أكثر إعلانية ومصممة لغرض محدد مثلuseDataأعلاه. كلما قل عدد استدعاءاتuseEffectالخام في مكوناتك، كلما وجدت صيانة تطبيقك أسهل.
ملخص
- إذا كان بإمكانك حساب شيء ما أثناء التصيير، فلست بحاجة إلى Effect.
- للتخزين المؤقت للحسابات المكلفة، أضف
useMemoبدلاً منuseEffect. - لإعادة تعيين حالة شجرة مكون كاملة، مرر لها
keyمختلفًا. - لإعادة تعيين جزء معين من الحالة استجابةً لتغيير في الخاصية، قم بتعيينه أثناء التصيير.
- الكود الذي يعمل لأن المكون كانمعروضًايجب أن يكون في Effects، والباقي يجب أن يكون في الأحداث.
- إذا كنت بحاجة إلى تحديث حالة عدة مكونات، فمن الأفضل القيام بذلك خلال حدث واحد.
- كلما حاولت مزامنة متغيرات الحالة في مكونات مختلفة، فكر في رفع الحالة لأعلى.
- يمكنك جلب البيانات باستخدام Effects، لكنك تحتاج إلى تنفيذ عملية تنظيف لتجنب ظروف السباق.
Try out some challenges
Challenge 1 of 4:Transform data without Effects #
The TodoList below displays a list of todos. When the “Show only active todos” checkbox is ticked, completed todos are not displayed in the list. Regardless of which todos are visible, the footer displays the count of todos that are not yet completed.
Simplify this component by removing all the unnecessary state and Effects.
