يجمع هذا المقال قائمة ببعض أكثر تقارير الأعطال غموضاً في React-Native، ويشرح أسبابها ويقترح الحلول الممكنة. أما الحكم على أي جانب من جوانب الإطار أو تقييم آلية عمل مكوّنات بعينها فهو خارج نطاق هذا المقال تماماً.
دفعني إلى كتابة هذا المقال مقدار الإحباط الذي واجهته أثناء تطوير تطبيق للأجهزة اللوحية أُطلق مؤخراً باستخدام React-Native. تتراوح مهامي المعتادة في فويغو بين تطوير المواقع والنماذج الأولية للواجهات وتطوير تطبيقات الجوال، وإن كانت الأخيرة لا تقع على عاتقي كثيراً لأن فريق التطوير يضم مطوّرين متخصصين في التطوير الأصلي لنظامي iOS وAndroid. لكننا اخترنا النهج الهجين لمواجهة ضيق الوقت وإطلاق التطبيق على المنصتين.
وضعني كون React-Native إطاراً شائعاً وسريع التطور تحت ضغط. فآخر مرة استخدمته فيها لتطوير تطبيق كانت قبل عامين، حين كان في الإصدار 0.43، وقد تغيّر الكثير منذ ذلك الحين: الأخطاء المعروضة، وواجهات الوحدات الأصلية، وتوقف دعم بعض الوحدات الخارجية، وغير ذلك..
ولحسن الحظ، كانت تجربتي الشخصية مع الإصدار الأخير رائعة في إدارة الحالة (Store) ومع تبنّي مبدأ العمل دون اتصال أولاً. أما ما ساء فهو اللحظات التي اضطررت فيها للتعامل مع تقارير أعطال لا تقول الكثير عن المشكلة.
تخص الملاحظات التي وثّقتها نظام Android تحديداً، ولا أعني أن Xcode كان سلساً تماماً، لكن أخطاءه على الأقل كانت واضحة ومباشرة.
من المزعج أن ينتهي بك الأمر إلى تتبّع أخطاء متقطعة لا يمكن إعادة إنتاجها. ومع تطبيق امتد إلى نحو 25 شاشة ويتصل بنحو 40 خدمة ويب، لم يكن بناء إصدار ينهار قبل أن يبدأ دون أن يترك أي أثر أو سجل علامة جيدة. وبعد العقبات التي واجهتها لحل هذه المشكلات، فكّرت في مشاركة ما توصلت إليه مع مجتمع المطوّرين.
رُتّبت تقارير الأعطال أدناه بحسب درجة غموضها.
1- Requested keys of a value that is not an object
هذا أسوأ الأعطال على الإطلاق رغم أن حله بسيط وسهل. فإن تتبّعت سجل الاستدعاءات، ستجد نفسك تعبث بملف «autoRehydrate.js». ولأن اسم الملف يشرح المشكلة، بدأت أشك في إعدادات redux-persist. وبما أن الخطأ لم يكن قابلاً لإعادة الإنتاج، كان من الصعب تتبّع مصدره، فظللت أتجاهله حتى بُني الإصدار الأول لبدء الاختبار الداخلي، فأصبح حينها مشكلة حقيقية؛ إذ انهار التطبيق قبل الإقلاع، ولم يكن أمر التسجيل «adb» مفيداً على الإطلاق لأن شيئاً لم يظهر على الشاشة.
المرة الوحيدة التي ناقش فيها مجتمع المطوّرين هذا العطل كانت حول مكتبة خارجية تستخدم خدمات AWS، ولا علاقة لها بحالتي، فاضطررت لعزل المشروع بأكمله والبدء بأبسط إعداد لـ redux. وتبيّن أن السبب هو سوء إعداد redux-offline في الـ Store، مما أفسد عمليتي حفظ الحالة وإعادة تحميلها. فالإعداد الموصى به لـ redux-offline يستخدم redux-persist v4 (الإصدار الأقدم)، بينما قررت استخدام v5 (الإصدار الأحدث) للاستفادة من ميزاته الجديدة، لكنني أغفلت اختلافات الإعداد في الإصدار الأحدث.
ولحسن الحظ، كانت الإعدادات الجديدة متاحة في redux-offline. احرص على تثبيت هذه الاعتمادية وإعدادها جيداً كي لا تعيش مع هذا الخطأ طويلاً.
2- Debug build failed
يسجّل هذا العطل الرسالة «FAILURE: Build failed with an exception» في الطرفية ويوقف عملية البناء لإصداري التصحيح والإنتاج.
وقد لا يكون الأمر بالنسبة لمطوّر الويب واضحاً كخطأ المتصفح لمعرفة ما حدث، لكن الحل بسيط للغاية.
ورغم أن هذا الخطأ لا علاقة له بـ React-Native، تمنيت شخصياً لو أن Gradle (أداة سطر الأوامر) يعرض رسالة شاملة توضّح الأمر. والشكر لمجتمع react-native الذي ساعد في تحديد المشكلة وقدّم الدعم.
3- Google services version check
عطل آخر يُسجَّل في الطرفية عند بناء إصدار الإنتاج. فكلما زاد عدد المكتبات الخارجية التي نستخدمها، زادت احتمالات مواجهة مشكلات توافق الإصدارات. وفي هذا الخطأ يُجري Gradle فحصاً آلياً لتوافق الإصدارات ويسجّل التحذيرات أو الأخطاء عند الحاجة، ما لم يُعطَّل هذا الفحص صراحةً. ولتعطيله، يجب إضافة الأمر التالي إلى build.gradle ضمن ./android/app/ (ويُفضّل في نهاية الملف):
com.google.gms.googleservices.GoogleServicesPlugin.config.disableVersionCheck = true
4- Text strings must be rendered within a <Text> component
خطأ آخر بسيط ومباشر لكن يصعب رصده، ويحدث عند ترك نص دون إحاطته بالمكوّن <Text> عن طريق الخطأ. وقد لا يبدو هذا الخطأ البسيط كبيراً، لكنه قد يتسبب في انهيار التطبيق إن مرّ دون انتباه وبُني للإنتاج.
قد يبدو الخطأ المذكور واضحاً، لكن حين يقع في مكوّنات متداخلة بعمق، يعجز React-Native عن إبلاغ المطوّر بموضعه الدقيق. وقد أمضيت شخصياً نحو 15 دقيقة أتنقّل يائساً بين المكوّنات المتداخلة لتحديد موضع الخلل. ومن الشائع الوقوع في هذا الخطأ عند استخدام التعبيرات الشرطية المختصرة أثناء العرض.
فكما ترى، استخدام «textArray.length» تعبيراً شرطياً يُعيد رقماً، فيُطلق الخطأ المذكور أعلاه. وبعد عدة محاولات وتجارب جانبية، توصيتي الوحيدة لتجنّب هذا العطل هي تحويل جميع التعبيرات الشرطية دائماً إلى النوع المنطقي (Boolean).
ولاحظ الدالة التي تسبق العرض؛ فـ «typeOf» يمكن أن تكون مساعداً جيداً للتحقق من نوع البيانات قبل تمرير كائن ما. ومع التعديلات السابقة، تُعيد دالة render النتائج المتوقعة مع مراعاة ممارسات البرمجة الآمنة.
5- Objects are not valid as React child. If you mean to render a collection of children, use an array instead.
ورغم قبح الأمر، كان مزعجاً للغاية أن أعرف لأول مرة أن React لا يدعم بعد العرض غير المتزامن. لكن ليس لوقت طويل، فهو يبدو ترقية كبيرة في المكتبة وفي دورة حياة المكوّنات، وسيطرح الفريق المطوّر لـ React هذه الميزة قريباً بدءاً من الإصدار 17.0. ويبدو أن الترقية ستكون تدريجية لأن Facebook يدير أكثر من 50 ألف مكوّن.
وفي الأثناء، النهج الذي يقترحه React بسيط للغاية: باختصار، استهلك البيانات غير المتزامنة بعد انتهاء تحميل المكوّن، واحفظها في الحالة («setState»)، فيُطلق ذلك مباشرة دورة العرض («render») ويحدّث الواجهة.
شكراً للقراءة.