الفرق بين المراجعتين ل"ReactNative/performance"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
ط
 
(6 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:تحسين الأداء في React Native}}</noinclude>
+
<noinclude>{{DISPLAYTITLE:تحسين الأداء في React Native}}</noinclude>أحد الأسباب المقنعة لاستخدام React Native بدلاً من أدواتٍ تعتمد على عارض الويب WebView هو الحصول على تطبيق يعمل بمعدّل 60 إطارًا في الثانية مع شكل ومظهر أصيلٍ. يعمل مطورو React Native على تطوير الإطار ليُنجز المهام بشكل صحيح وفعّال، ليساعدك ذلك على التركيز على تطبيقك بدلاً من تحسين الأداء، ولكن هناك نقصٌ في بعض المناطق، وفي مناطق أخرى لا يُمكن فيها لإطار العمل (كما هو الحال مع كتابة شيفرة باللغة الأصيلة) أن يُحدّد أفضل طريقة للتحسين من أجلك، وبالتالي سيكون التدخل اليدوي ضروريًا. يبذل مطورو إطار العمل قصارى جهدهم لتقديم أداء واجهة مستخدم سلسَةٍ وسريعة افتراضيًّا، لكن أحيانًا لا يكون هذا ممكنًا، لذا عليك أن تُحسّن أداء تطبيقك بنفسك في هذه الحالات.
==الأداء==
 
أحد الأسباب المقنعة لاستخدام React Native بدلاً من أدواتٍ تعتمد على عروض WebView هو الحصول على تطبيق يعمل بمعدّل 60 إطارًا في الثانية مع شكل ومظهر أصيلٍ. يعمل مطورو React Native على تطوير الإطار ليُنجز المهام بشكل صحيح وفعّال، ليساعدك ذلك على التركيز على تطبيقك بدلاً من تحسين الأداء، ولكن هناك نقصٌ في بعض المناطق، وفي مناطق أخرى لا يُمكن فيها لإطار العمل (كما هو الحال مع كتابة شيفرة باللغة الأصيلة) أن يُحدّد أفضل طريقة للتحسين من أجلك، وبالتالي سيكون التدخل اليدوي ضروريًا. يبذل مطورو إطار العمل قصارى جهدهم لتقديم أداء واجهة مستخدم سلسَةٍ وسريعة افتراضيًّا، لكن أحيانًا لا يكون هذا ممكنًا، لذا عليك أن تُحسّن أداء تطبيقك بنفسك في هذه الحالات.
 
  
يهدف هذا الدليل إلى تعليمك بعض الأساسيات لمساعدتك على استكشاف مشاكل الأداء وإصلاحها، وسنناقش كذلك المصادر الشائعة للمشاكل والحلول المقترحة لها.
+
يهدف هذا الدليل إلى تعليمك بعض الأساسيات لمساعدتك على [[ReactNative/performance#.D8.AA.D8.B9.D8.B1.D9.8A.D9.81 .D8.A7.D9.84.D8.A3.D8.AF.D8.A7.D8.A1 .28Profiling.29|استكشاف مشاكل الأداء]] وإصلاحها، وسنناقش كذلك [[ReactNative/performance#.D9.85.D8.B5.D8.A7.D8.AF.D8.B1 .D8.B4.D8.A7.D8.A6.D8.B9.D8.A9 .D9.84.D9.85.D8.B4.D8.A7.D9.83.D9.84 .D8.A7.D9.84.D8.A3.D8.AF.D8.A7.D8.A1|المصادر الشائعة للمشاكل والحلول المقترحة]] لها.
  
 
==ما تحتاج إلى معرفته حول الإطارات (frames)==
 
==ما تحتاج إلى معرفته حول الإطارات (frames)==
سطر 12: سطر 10:
  
 
===معدل إطارات JS (سلسلة JavaScript)===
 
===معدل إطارات JS (سلسلة JavaScript)===
في معظم تطبيقات React Native، سيُنفَّذ منطق معاملاتك (business logic) على السلسلة (thread) الخاصّة بلغة JavaScript. هذه هي المنطقة التي يوجد فيها تطبيق React الخاص بك، وهنا تُجرى استدعاءات الواجهات البرمجيّة ومعالجة الأحداث، وما إلى ذلك... تُجمَّع التحديثات التي تتعلّق بالعروض الأصيلة (native-backed views) وتُرسَل إلى الجانب الأصيل في نهاية كل تكرار من تكرارات حلقة الأحداث (event loop) قبل الموعد النهائي للإطار (هذا إذا سارت الأمور على ما يرام). إذا لم تستجب سلسلة JavaScript لإطارٍ ما، فسَيُعَدُّ إطارًا منسدلًا (dropped frame). على سبيل المثال، إذا استدعيت الدالة <code>this.setState</code> على المكون الجذر لتطبيقٍ معقّد، وأدّى ذلك إلى إعادة تصيير مكوّناتٍ فرعيةٍ تأخذ الكثير من الوقت بسبب إجراء حسابات معقّدة، فمن المرجّح أن هذا قد يستغرق 200 مللي ثانية ويؤدي إلى إسقاط 12 إطارًا. ستظهر أي تحريكاتٍ مُتحّكَمٍ فيها من طرف JavaScript مُجمَّدَةً خلال تلك الفترة. إذا استغرق أي شيء أكثر من 100 مللي ثانية، فسيشعر المستخدم بذلك.
+
في معظم تطبيقات React Native، سيُنفَّذ منطق معاملاتك (business logic) على السلسلة (thread) الخاصّة بلغة JavaScript. هذه هي المنطقة التي يوجد فيها تطبيق React الخاص بك، وهنا تُجرى استدعاءات الواجهات البرمجيّة ومعالجة الأحداث، وما إلى ذلك. تُجمَّع التحديثات التي تتعلّق بالواجهات الأصيلة (native-backed views) وتُرسَل إلى الجانب الأصيل في نهاية كل تكرار من تكرارات حلقة الأحداث (event loop) قبل الموعد النهائي للإطار (هذا إذا سارت الأمور على ما يرام). إذا لم تستجب سلسلة JavaScript لإطارٍ ما، فسَيُعَدُّ إطارًا منسدلًا (dropped frame). على سبيل المثال، إذا استدعيت الدالة <code>this.setState</code> على المكون الجذر لتطبيقٍ معقّد، وأدّى ذلك إلى إعادة تصيير مكوّناتٍ فرعيةٍ تأخذ الكثير من الوقت بسبب إجراء حسابات معقّدة، فمن المرجّح أن هذا قد يستغرق 200 مللي ثانية ويؤدي إلى إسقاط 12 إطارًا. ستظهر أي تحريكاتٍ مُتحّكَمٍ فيها من طرف JavaScript مُجمَّدَةً خلال تلك الفترة. إذا استغرق أي شيء أكثر من 100 مللي ثانية، فسيشعر المستخدم بذلك.
  
يحدث هذا غالبًا أثناء انتقالات <code>Navigator</code>: عندما تدفعُ موجّهًا (push a route) جديدًا، ستحتاج سلسلة JavaScript إلى تصيير جميع المكونات اللازمة للمشهد لإرسال الأوامر الصحيحة إلى الجانب الأصيل لإنشاء العروض الجديدة. من الشائع أن تأخذ هذه العمليّة بعض الإطارات ومن الممكن أن يُسبب ذلك في حدوث ظاهرة jank (أي ظهور التطبيق وكأنه لا يستجيب لتفاعل المستخدم معه) لأن الانتقال مُتحّكَمٌ فيه من طرف سلسلة JavaScript. في بعض الأحيان، تؤدّي المكونات عملًا إضافيًّا في التابع <code>componentDidMount</code>، مما قد يؤدي إلى تأخّرٍ (أو تأتأةٍ: stutter) ثانٍ في الانتقال.
+
يحدث هذا غالبًا أثناء انتقالات <code>Navigator</code>: عندما تدفعُ موجّهًا (push a route) جديدًا، ستحتاج سلسلة JavaScript إلى تصيير جميع المكونات اللازمة للمشهد لإرسال الأوامر الصحيحة إلى الجانب الأصيل لإنشاء الواجهات الجديدة. من الشائع أن تأخذ هذه العمليّة بعض الإطارات ومن الممكن أن يُسبب ذلك في حدوث ظاهرة jank (أي ظهور التطبيق وكأنه لا يستجيب لتفاعل المستخدم معه) لأن الانتقال مُتحّكَمٌ فيه من طرف سلسلة JavaScript. في بعض الأحيان، تؤدّي المكونات عملًا إضافيًّا في التابع <code>[[React/react component#componentDidMount.28.29.E2.80.8E|componentDidMount]]</code>، مما قد يؤدي إلى تأخّرٍ (أو تأتأةٍ: stutter) ثانٍ في الانتقال.
  
الاستجابة لللمسات مثالٌ آخر: إذا كنت تقوم بعملياتٍ عبر عدّة إطارات في سلسلة JavaScript، فقد تلاحظ وجود تأخيرٍ في الاستجابة إلى المكوّن <code>TouchableOpacity</code> مثلًا. ويرجع ذلك إلى أن سلسلة  JavaScript مشغولة ولا يمكنها معالجة أحداث اللمس الأولية المرسلة من السلسلة الرئيسيّة. والنتيجة أنّ المكوّن <code>TouchableOpacity</code> غير قادرٍ على التّفاعل مع أحداث اللمس لإخبار العرض الأصيل بتغيير عتامته (opacity).
+
الاستجابة للمسات مثالٌ آخر: إذا كنت تقوم بعملياتٍ عبر عدّة إطارات في سلسلة JavaScript، فقد تلاحظ وجود تأخيرٍ في الاستجابة إلى المكوّن <code>[[ReactNative/touchableopacity|TouchableOpacity]]</code> مثلًا. ويرجع ذلك إلى أن سلسلة  JavaScript مشغولة ولا يمكنها معالجة أحداث اللمس الأولية المرسلة من السلسلة الرئيسيّة. والنتيجة أنّ المكوّن <code>TouchableOpacity</code> غير قادرٍ على التّفاعل مع أحداث اللمس لإخبار الواجهة الأصيلة بتغيير عتامتها (opacity).
  
 
=== معدل إطارات واجهة المستخدم (السلسلة الرئيسية)===
 
=== معدل إطارات واجهة المستخدم (السلسلة الرئيسية)===
 
لاحظ العديد من الأشخاص أن أداء المكوّن ‎<code>NavigatorIOS</code>‎ أفضلُ من المكوّن ‎<code>Navigator</code>‎ (الذي أُزيل من إطار العمل منذ مُدّة). سبب هذا هو أنّ تحريكات الانتقال تتم كليًّا على السلسلة الرئيسية، ولذلك لا تُقاطَع من طرف الإطارات المنسدلة على سلسلة JavaScript.
 
لاحظ العديد من الأشخاص أن أداء المكوّن ‎<code>NavigatorIOS</code>‎ أفضلُ من المكوّن ‎<code>Navigator</code>‎ (الذي أُزيل من إطار العمل منذ مُدّة). سبب هذا هو أنّ تحريكات الانتقال تتم كليًّا على السلسلة الرئيسية، ولذلك لا تُقاطَع من طرف الإطارات المنسدلة على سلسلة JavaScript.
  
وبالمثل، يمكنك التمرير لأعلى ولأسفل في عرضِ <code>ScrollView</code> عندما تُقفَل سلسلة JavaScript لأن مكوّن <code>ScrollView</code> موجود في السلسلة الرئيسية. تُرسَل أحداث التمرير (scroll events) إلى سلسلة JavaScript، لكنّ استقبالها ليس ضروريًا ليحدث التمرير.
+
وبالمثل، يمكنك التمرير لأعلى ولأسفل في واجهة <code>[[ReactNative/scrollview|ScrollView]]</code> عندما تُقفَل سلسلة JavaScript لأن مكوّن <code>ScrollView</code> موجود في السلسلة الرئيسية. تُرسَل أحداث التمرير (scroll events) إلى سلسلة JavaScript، لكنّ استقبالها ليس ضروريًا ليحدث التمرير.
  
 
==مصادر شائعة لمشاكل الأداء==
 
==مصادر شائعة لمشاكل الأداء==
سطر 27: سطر 25:
 
===تشغيل التطبيق في وضع التطوير (<code>dev=true</code>)===
 
===تشغيل التطبيق في وضع التطوير (<code>dev=true</code>)===
  
يتأثر أداء سلسلة JavaScript بشدّةٍ عند التشغيل في وضع التطوير. هذا أمرٌ لا مفر منه، إذ يجب القيام بالكثير من العمل في وقت التشغيل (runtime) لتزويدك بتحذيرات ورسائل خطأ جيدة، مثل التحقق من أنواع <code>propTypes</code> ومختلف التأكيدات (assertions) الأخرى. تأكد دائمًا من اختبار الأداء في بناءات الإصدار (release builds).
+
يتأثر أداء سلسلة JavaScript بشدّةٍ عند التشغيل في وضع التطوير. هذا أمرٌ لا مفر منه، إذ يجب القيام بالكثير من العمل في وقت التشغيل (runtime) لتزويدك بتحذيرات ورسائل خطأ جيدة، مثل التحقق من أنواع propTypes ومختلف التأكيدات (assertions) الأخرى. تأكد دائمًا من اختبار الأداء في [[ReactNative/running on device|بناءات الإصدار]] (release builds).
  
 
=== استخدام جمل <code>console.log</code> ===
 
=== استخدام جمل <code>console.log</code> ===
  
عند تشغيل تطبيق مُحزَّم (bundled app)، يمكن أن تتسبب جمل <code>console.log</code> في حدوث خنق كبير في سلسلة JavaScript. وهذا يشمل الاستدعاءات من مكتبات التنقيح مثل [https://github.com/evgenyrodionov/redux-logger redux-logger]، لذا تأكد من إزالتها قبل التحزيم. يمكنك كذلك استخدام [https://babeljs.io/docs/plugins/transform-remove-console/ إضافة babel هذه] التي تزيل جميع استدعاءات ‎<code>console.*</code>‎. ستحتاج إلى تثبيتها أولاً باستخدام ‎<code>npm i babel-plugin-transform-remove-console --save-dev</code>‎، ثم عدِّل ملف ‎<code>.babelrc</code>‎ داخل مشروعك على النحو التالي:
+
عند تشغيل تطبيق مُجمّع (bundled app)، يمكن أن تتسبب جمل console.log  في حدوث خنق كبير في سلسلة JavaScript. وهذا يشمل الاستدعاءات من مكتبات التنقيح مثل [https://github.com/evgenyrodionov/redux-logger redux-logger]، لذا تأكد من إزالتها قبل التحزيم. يمكنك كذلك استخدام [https://babeljs.io/docs/plugins/transform-remove-console/ إضافة babel هذه] التي تزيل جميع استدعاءات ‎<code>console.*</code>‎. ستحتاج إلى تثبيتها أولاً باستخدام ‎<code>npm i babel-plugin-transform-remove-console --save-dev</code>‎، ثم عدِّل ملف ‎<code>.babelrc</code>‎ داخل مشروعك على النحو التالي:
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
{
 
{
سطر 43: سطر 41:
 
سيؤدي هذا إلى إزالة كافة استدعاءات ‎<code>console.*</code>‎ في نسخ الإصدار (أو الإنتاج [production]) من مشروعك.
 
سيؤدي هذا إلى إزالة كافة استدعاءات ‎<code>console.*</code>‎ في نسخ الإصدار (أو الإنتاج [production]) من مشروعك.
  
===تصيير مكون ListView الأولي بطيء جدا أو أن أداء التمرير سيء للقوائم الكبيرة===
+
===تصيير مكون ListView الأولي بطيء جدًا أو أن أداء تمرير القوائم الكبيرة سيئ===
استخدم مكون <code>FlatList</code> أو <code>SectionList</code> الجديدين بدلاً من المكون القديم <code>ListView</code> الذي أصبح مُهملًا (deprecated). إلى جانب تبسيط واجهة برمجة التطبيقات (API)، تحتوي مكونات القوائم الجديدة كذلك على تحسيناتِ أداءٍ كبيرة، أكبر هذه التحسينات هو استخدام نفس حجم الذاكرة تقريبًا لأي عدد من الصفوف.
+
استخدم مكون <code>[[ReactNative/flatlist|FlatList]]</code> أو <code>[[ReactNative/sectionlist|SectionList]]</code> الجديدين بدلًا من المكون القديم <code>ListView</code> الذي أصبح مُهملًا (deprecated). إلى جانب تبسيط واجهة برمجة التطبيقات (API)، تحتوي مكونات القوائم الجديدة كذلك على تحسيناتِ أداءٍ كبيرة، أكبر هذه التحسينات هو استخدام نفس حجم الذاكرة تقريبًا لأي عدد من الصفوف.
  
 
إذا كان تصيير المكوّن <code>FlatList</code> بطيئًا في تطبيقك، فاستعمل الخاصيّة <code>getItemLayout</code> لتحسين سرعة التصيير عبر تخطي قياس العناصر المصيَّرة.
 
إذا كان تصيير المكوّن <code>FlatList</code> بطيئًا في تطبيقك، فاستعمل الخاصيّة <code>getItemLayout</code> لتحسين سرعة التصيير عبر تخطي قياس العناصر المصيَّرة.
  
===ينخفض معدّل إطارات JS في الثانيّة (JS FPS) عند إعادة تصيير مكون لا يتغير تقريبا===
+
===ينخفض معدل إطارات JS في الثانيّة (JS FPS) عند إعادة تصيير مكون لا يتغير تقريبا===
إذا كنت تستخدم مكوّن <code>ListView</code>، عليك توفير دالة <code>rowHasChanged</code> التي يمكن لها أن تختصر الكثير من الجهد بتحديد ما إذا كان الصف يحتاج إلى إعادة تصييره سريعًا. إذا كنت تستخدم هياكل بيانات غير قابلة للتغيير (immutable data structures)، فسيكون هذا بسيطًا عبر التحقق من المساواة المرجعية (reference equality).
+
إذا كنت تستخدم مكوّن <code>[[ReactNative/listview|ListView]]</code>، عليك توفير دالة <code>rowHasChanged</code> التي يمكن لها أن تختصر الكثير من الجهد بتحديد ما إذا كان الصف يحتاج إلى إعادة تصييره سريعًا. إذا كنت تستخدم هياكل بيانات غير قابلة للتغيير (immutable data structures)، فسيكون هذا بسيطًا عبر التحقق من المساواة المرجعية (reference equality).
  
وبالمثل، يمكنك استخدام <code>shouldComponentUpdate</code> والإشارة إلى الشروط الدقيقة التي ترغب بإعادة تصيير المكون بها. إذا كنت تكتب مكونات نقية (حيث تعتمد القيمة المعادة [return value] لدالة التصيير [render function] اعتمادًا كاملًا على الخاصيات والحالة)، يمكنك الاستفادة من المكون <code>PureComponent</code> للقيام بذلك نيابة عنك. مجدّدًا، هياكل البيانات غير القابلة للتغيير مفيدة للحفاظ على سرعة الأداء، إذا توجَّب عليك القيام بمقارنةٍ عميقةٍ (deep comparison) لقائمة كبيرة من الكائنات، فقد تكون إعادة تصيير المكون بأكمله أسرع، وسيتطلب ذلك شيفرةً أقل بالتأكيد.
+
وبالمثل، يمكنك استخدام <code>[[React/react component#shouldComponentUpdate.28.29.E2.80.8E|shouldComponentUpdate]]</code> والإشارة إلى الشروط الدقيقة التي ترغب بإعادة تصيير المكون بها. إذا كنت تكتب مكونات نقية (حيث تعتمد القيمة المعادة [return value] لدالة التصيير [render function] اعتمادًا كاملًا على الخاصيات والحالة)، يمكنك الاستفادة من المكون <code>PureComponent</code> الصرف للقيام بذلك نيابة عنك. مجدّدًا، هياكل البيانات غير القابلة للتغيير مفيدة للحفاظ على سرعة الأداء، إذا توجَّب عليك القيام بمقارنةٍ عميقةٍ (deep comparison) لقائمة كبيرة من الكائنات، فقد تكون إعادة تصيير المكون بأكمله أسرع، وسيتطلب ذلك شيفرةً أقل بالتأكيد.
  
 
=== سقوط إطارات على سلسلة JS بسبب إجراء عمليات كثيرة على سلسلة JS في نفس الوقت===
 
=== سقوط إطارات على سلسلة JS بسبب إجراء عمليات كثيرة على سلسلة JS في نفس الوقت===
التنقّل البطيء في المتنقّل (Navigator) من المظاهر الشائعة لهذه المشكلة، لكن هناك حالات أخرى يحدث فيها هذا. يمكن لاستخدام واجهة <code>InteractionManager</code> البرمجيّة أن يكون طريقة جيدة لحلّها، لكن إذا كان تأخير تحريكٍ مُكلّفًا على تجربة المستخدم. فانظر واجهة <code>LayoutAnimation</code> البرمجية.
+
التنقّل البطيء في المتنقّل (Navigator) من المظاهر الشائعة لهذه المشكلة، لكن هناك حالات أخرى يحدث فيها هذا. يمكن لاستخدام واجهة InteractionManager البرمجيّة أن يكون طريقة جيدة لحلّها، لكن إذا كان تأخير تحريكٍ مُكلّفًا على تجربة المستخدم. فانظر واجهة LayoutAnimation البرمجية.
  
تحسب واجهة <code>Animated</code> حاليًا كل إطارٍ مفتاحيّ (keyframe) حسب الطلب (on-demand) على سلسلة JavaScript، إلا إذا قمت بضبط <code>useNativeDriver: true</code>‎، في حين أن واجهة <code>LayoutAnimation</code> تعتمد على التحريكات الأساسية (Core Animation) ولا تتأثر بسقوط الإطارات في سلسلة JS والسلسلة الرئيسية.
+
تحسب واجهة [[ReactNative/animated|Animated]] حاليًا كل إطارٍ مفتاحيّ (keyframe) حسب الطلب (on-demand) على سلسلة JavaScript، إلا إذا قمت بضبط <code>‎[https://reactnative.dev/blog/2017/02/14/using-native-driver-for-animated#how-do-i-use-this-in-my-app useNativeDriver: true]‎</code>، في حين أن واجهة LayoutAnimation تعتمد على التحريكات الأساسية (Core Animation) ولا تتأثر بسقوط الإطارات في سلسلة JS والسلسلة الرئيسية.
  
هذه حالة يُمكن فيها استخدام هذه الطريقة: التحريك في مربّع حوار (التمرير لأسفل من أعلى مع التلاشي في تراكبٍ نصف شفاف) أثناء التهيئة وربما تلقّي استجابات لطلباتٍ متعددة من الشبكة، وتقديم محتويات النافذة، وكذا تحديث العرض الذي فُتحت منه النافذة. انظر دليل "التحريكات" لمزيد من المعلومات حول كيفية استخدام واجهة <code>LayoutAnimation</code>.
+
هذه حالة يُمكن فيها استخدام هذه الطريقة: التحريك في مربّع حوار (التمرير لأسفل من أعلى مع التلاشي في تراكبٍ نصف شفاف) أثناء التهيئة وربما تلقّي استجابات لطلباتٍ متعددة من الشبكة، وتقديم محتويات النافذة، وكذا تحديث العرض الذي فُتحت منه النافذة. انظر دليل "[[ReactNative/animations|التحريكات]]" لمزيد من المعلومات حول كيفية استخدام واجهة <code>LayoutAnimation</code>.
  
'''محاذير:'''
+
'''تحذير:''' لا يعمل LayoutAnimation إلا مع التحريكات الساكنة (‎"static"‎ animations‎)، أي تلك التي تبدأ وتستمر في العمل دون حاجة إلى تغييرها. إذا لزِمَت مقاطعة التحريكات، فستحتاج إلى استخدام واجهة ‎<code>Animated</code>‎.
* لا يعمل <code>LayoutAnimation</code> إلا مع التحريكات الساكنة (‎"static"‎ animations‎)، أي تلك التي تبدأ وتستمر في العمل دون حاجة إلى تغييرها. إذا لزِمَت مقاطعة التحريكات، فستحتاج إلى استخدام واجهة ‎<code>Animated</code>‎.
+
===نقل واجهة على الشاشة (التمرير، الترجمة، التدوير) يسقط معدل الإطارات على سلسلة واجهة المستخدم===
 +
يحدث هذا بشكل خاص عندما يكون لديك نص بخلفية شفّافة موضوعٍ فوق صورة، أو أي حالة أخرى تتطلب تركيب ألفا (alpha compositing) لإعادة رسم الواجهة على كل إطار. قد تجد أنّ تمكينَ ‎<code>shouldRasterizeIOS</code>‎ أو ‎<code>renderToHardwareTextureAndroid</code>‎ يساعدُ بشكل ملحوظ.
  
===نقل عرض على الشاشة (التمرير، الترجمة، التدوير) يسقط معدل الإطارات على سلسلة واجهة المستخدم===
+
اِحرص على عدم الإفراط في استخدام هذه الحيلة، فقد تستنزف ذاكرة الهاتف. افحص حالة الأداءِ وحالة استخدامِ الذاكرة عند الاعتماد على هذه الخاصيات. إذا كنت تُخطّط لإيقاف تحريك الواجهة، فعطّل هذه الخاصية.
يحدث هذا بشكل خاص عندما يكون لديك نص بخلفية شفّافة موضوعٍ فوق صورة، أو أي حالة أخرى تتطلب تركيب ألفا (alpha compositing) لإعادة رسم العرض على كل إطار. قد تجد أنّ تمكينَ ‎<code>shouldRasterizeIOS</code>‎ أو ‎<code>renderToHardwareTextureAndroid</code>‎ يساعدُ بشكل ملحوظ.
 
 
 
اِحرص على عدم الإفراط في استخدام هذه الحيلة، فقد تستنزف ذاكرة الهاتف. افحص حالة الأداءِ وحالة استخدامِ الذاكرة عند الاعتماد على هذه الخاصيات. إذا كنت تُخطّط لإيقاف تحريك العرض، فعطّل هذه الخاصية.
 
  
 
===يسقط تحريك حجم صورة معدل الإطارات على سلسلة واجهة المستخدم===
 
===يسقط تحريك حجم صورة معدل الإطارات على سلسلة واجهة المستخدم===
على iOS، في كل مرة تغيّر فيها ضبط عرض أو ارتفاع مكوّن <code>Image</code>، يُعاد اقتصاصها وتحجيمها من الصورة الأصلية. قد يكون هذا مكلفًا جدا، خاصّة للصور الكبيرة. بدلاً من هذا، اِستخدم خاصية النمط ‎<code>transform: [{scale}]</code>‎ لتحريك الحجم. النقر فوق صورة وتكبيرها إلى وضع ملء الشاشة من الأمثلة على حالة من حالات الاعتماد على هذه الخاصية.
+
على iOS، في كل مرة تغيّر فيها ضبط عرض أو ارتفاع مكوّن Image، يُعاد اقتصاصها وتحجيمها من الصورة الأصلية. قد يكون هذا مكلفًا جدا، خاصّة للصور الكبيرة. بدلاً من هذا، اِستخدم خاصية النمط ‎<code>transform: [{scale}]</code>‎ لتحريك الحجم. النقر فوق صورة وتكبيرها إلى وضع ملء الشاشة من الأمثلة على حالة من حالات الاعتماد على هذه الخاصية.
  
===عرض ‎<code>TouchableX</code>‎ لا يستجيب بسرعة===
+
===واجهة ‎TouchableX‎ لا تستجيب بسرعة===
أحيانًا، إذا أجرينا عمليّة في نفس الإطار الذي نُعدّل فيه العتامة (opacity) أو الإبراز (highlight) لأحد المكونات التي تستجيب إلى اللمس، فلن نرى هذا التأثير إلا بعد أن تُعيد الدالة <code>onPress</code>. إذا كانت الدالة <code>onPress</code> تغيّر الحالة بالدالة <code>setState</code>، ما يحتاج إلى عمليات مكلّفة ويسبّب سقوط بعض الإطارات، فقد يبدو المكون القابل لللمس غير مستجيب. أحد حلول هذه المشكلة هو تغطية أي عمليات داخل معالج الدالة <code>onPress</code> بالدالة <code>requestAnimationFrame</code> كما يلي (لكن استخدم دائمًا مخلوط <code>TimerMixin</code> مع كل من <code>requestAnimationFrame</code>، و<code>setTimeout</code>، و<code>setInterval</code>، انظر صفحة المؤقتات):
+
أحيانًا، إذا أجرينا عمليّة في نفس الإطار الذي نُعدّل فيه العتامة (opacity) أو الإبراز (highlight) لأحد المكونات التي تستجيب إلى اللمس، فلن نرى هذا التأثير إلا بعد أن تُعيد الدالة <code>onPress</code>. إذا كانت الدالة <code>onPress</code> تغيّر الحالة بالدالة <code>setState</code>، ما يحتاج إلى عمليات مكلّفة ويسبّب سقوط بعض الإطارات، فقد يبدو المكون القابل للمس غير مستجيب. أحد حلول هذه المشكلة هو تغطية أي عمليات داخل معالج الدالة <code>onPress</code> بالدالة <code>requestAnimationFrame</code> كما يلي:
 
<syntaxhighlight lang="javascript">
 
<syntaxhighlight lang="javascript">
 
handleOnPress() {
 
handleOnPress() {
   // Always use TimerMixin with requestAnimationFrame, setTimeout and
+
   requestAnimationFrame(() => {
  // setInterval
 
  this.requestAnimationFrame(() => {
 
 
     this.doExpensiveAction();
 
     this.doExpensiveAction();
 
   });
 
   });
سطر 84: سطر 78:
  
 
=== انتقالات بطيئة في مكون Navigator===
 
=== انتقالات بطيئة في مكون Navigator===
كما ذُكِر أعلاه، يُتحكَّمُ في تحريكات <code>Navigator</code> بواسطة سلسلة JavaScript. تخيل مشهد الانتقال "من اليمين إلى اليسار": يُنقَل المشهد الجديد في كل إطار من اليمين إلى اليسار، بدءًا من خارج الشاشة (عند نقطة <code>x-offset</code> تساوي 320 مثلًا) إلى أن يستقّر في النهاية عند نقطة <code>x-offset</code> تساوي 1.
+
كما ذُكِر أعلاه، يُتحكَّمُ في تحريكات <code>[[ReactNative/navigation|Navigator]]</code> بواسطة سلسلة [[JavaScript]]. تخيل مشهد الانتقال "من اليمين إلى اليسار": يُنقَل المشهد الجديد في كل إطار من اليمين إلى اليسار، بدءًا من خارج الشاشة (عند نقطة <code>x-offset</code> تساوي 320 مثلًا) إلى أن يستقّر في النهاية عند نقطة <code>x-offset</code> تساوي 1.
  
 
في كل إطار أثناء هذا النقل، ستحتاج سلسلة JavaScript إلى إرسال نقطة <code>x-offset</code> جديدة إلى السلسلة الرئيسية. إذا كانت سلسلة JavaScript مُقفلَةً (locked up)، فلا يمكن القيام بهذا، وبالتالي لا يحدث أي تحديث على ذلك الإطار وستتأخّر التحريكات.
 
في كل إطار أثناء هذا النقل، ستحتاج سلسلة JavaScript إلى إرسال نقطة <code>x-offset</code> جديدة إلى السلسلة الرئيسية. إذا كانت سلسلة JavaScript مُقفلَةً (locked up)، فلا يمكن القيام بهذا، وبالتالي لا يحدث أي تحديث على ذلك الإطار وستتأخّر التحريكات.
سطر 90: سطر 84:
 
أحد الحلول لهذه المشكلة هو السماح للتحريكات المعتمدة على JavaScript بنقل تحميلها (offloaded) إلى السلسلة الرئيسية. لو كتبنا نفس المثال أعلاه بهذا الأسلوب، فمن الممكن مثلا أن نحسب قائمة بجميع نقاط <code>x-offsets</code> للمشهد الجديد عندما نبدأ الانتقال، ثمّ نرسلها إلى السلسلة الرئيسية لتنفيذها بطريقة محسنة. الآن بعد تحرير سلسلة JavaScript من هذه المسؤولية، لن يكون سقوط بعض الإطارات أثناء تصيير المشهد مشكلة كبيرة. قد لا تلاحظ ذلك حتى، لأنّ الانتقال الجميل سيُشتّت انتباهك.
 
أحد الحلول لهذه المشكلة هو السماح للتحريكات المعتمدة على JavaScript بنقل تحميلها (offloaded) إلى السلسلة الرئيسية. لو كتبنا نفس المثال أعلاه بهذا الأسلوب، فمن الممكن مثلا أن نحسب قائمة بجميع نقاط <code>x-offsets</code> للمشهد الجديد عندما نبدأ الانتقال، ثمّ نرسلها إلى السلسلة الرئيسية لتنفيذها بطريقة محسنة. الآن بعد تحرير سلسلة JavaScript من هذه المسؤولية، لن يكون سقوط بعض الإطارات أثناء تصيير المشهد مشكلة كبيرة. قد لا تلاحظ ذلك حتى، لأنّ الانتقال الجميل سيُشتّت انتباهك.
  
حل هذه المشكلة أحد الأهداف الرئيسية لمكتبة [https://facebook.github.io/react-native/docs/navigation React Navigation] الجديدة. تَستخدِم العروض في React Navigation مكوناتٍ أصيلة ومكتبة <code>Animated</code> لتقديم تحريكاتٍ بمعدّل 60 إطارًا في الثانية تُشغَّل على السلسلة الأصيلة.
+
حل هذه المشكلة أحد الأهداف الرئيسية لمكتبة [[ReactNative/navigation|React Navigation]] الجديدة. تَستخدِم الواجهات في React Navigation مكوناتٍ أصيلة ومكتبة <code>[[ReactNative/animated|Animated]]</code> لتقديم تحريكاتٍ بمعدّل 60 إطارًا في الثانية تُشغَّل على السلسلة الأصيلة.
 
 
==تعريف الأداء (Profiling)==
 
استخدم أداة التعريف (profiler) المُضَمَّنَة للحصول على معلومات مفصّلة حول العمل المنجز في سلسلة JavaScript والسلسلة الرئيسية جنبًا إلى جنب. يمكن الوصول إلى الأداة عن طريق تحديد Perf Monitor من قائمة Debug.
 
 
 
في iOS، أداةُ Instruments أداةٌ لا تقدر بثمن، أمّا في نظام Android، عليك أن تتعلم كيفيّة استخدام <code>systrace</code>.
 
 
 
لكن أولًا، تأكّد من أن وضع التطوير (Development Mode) مُعطَّل، يجب أن ترى العبارة ‎<code>__DEV__ === false, development-level warning are OFF, performance optimizations are ON</code>‎ في سجلات تطبيقك.
 
 
 
هناك طريقة أخرى لتعريف الأداء في JavaScript، وهي استخدام أداة تعريف Chrome أثناء تصحيح الأخطاء. لن يمنحك هذا نتائج دقيقة لأنّ الشيفرة تُشغّل في Chrome، ولكنه سيعطيك فكرة عامة عن مواضع الاختناقات التي تعيق الأداء. شغِّل أداة Profiler ضمن علامة التبويب <code>"Performance"</code> في Chrome. سوف يظهر رسم بياني تحت عنوان ‎<code>User Timing</code>‎. لعرض مزيد من التفاصيل في جدول، انقر فوق علامة التبويب ‎<code>Bottom Up</code>‎ في الأسفل، ثم حدد <code>"DedicatedWorker Thread"</code> في أعلى القائمة اليسرى.
 
 
 
=== تعريف أداء واجهة المستخدم في Android بأداة Systrace ===
 
يدعم Android آلاف الهواتف المختلفة وهو نظام مُعمَّم لدعم التصيير البرمجي (software rendering): بنية الإطار والحاجة إلى التعميم عبر العديد من الأجهزة المستهدفة يعني أنّك تحصل على مميّزات أقل مقارنة بنظام iOS. لكن أحيانا هناك أمور يمكن تحسينها - وفي أحيان كثيرة لا يكون الخطأ في الشيفرة الأصيلة على الإطلاق!
 
 
 
الخطوة الأولى لتصحيح تأخر الإطارات هي الإجابة عن السؤال الأساسي:ما الذي يفعله التطبيق في فترة 16ms في كل إطار. للإجابة على هذا السؤال، سنستخدم أداة Android قياسية لتعريف الأداء تسمى <code>systrace</code>.
 
 
 
<code>systrace</code> أداةُ تعريف قياسية في Android تعتمد على العلامات (marker-based) وتُثبَّت عند تثبيت حزمة أدوات (platform-tools) نظام Android. تحاط كُتَل الشيفرة المُعرَّفة (Profiled code blocks)  بعلامات بدءٍ (start) ونهاية (end)، وتُمثَّل في مخطط بيانيّ ملون. يوفر كل من Android SDK و React Native علامات قياسية (standard markers) يمكن تمثيلها.
 
 
 
==== 1. جمع نقاط التعقب (trace)====
 
أولاً، قم بتوصيل جهازٍ تظهر فيه مشكلة تأخر الإطارات التي تريد التحقيق فيها مع حاسوبك عبر USB وانتقل في التطبيق إلى مكان التحريك أو الانتقال الذي تريد تعريف أدائه. ثمّ شغّل أداة <code>systrace</code> على النحو التالي:
 
<syntaxhighlight lang="javascript">
 
$ <path_to_android_sdk>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <your_package_name>
 
</syntaxhighlight>
 
استبدل ‎<code><path_to_android_sdk></code>‎ بمسار Android SDK في حاسبوك و‎<code><your_package_name></code>‎ باسم حزمة التطبيق، يُمكنك العثور على هذا الاسم في ملفّ ‎<code>AndroidManifest.xml</code>‎ الخاص بتطبيقك، ويبدو على الشكل ‎<code>com.example.app</code>‎.
 
 
 
شرح سريع لهذا الأمر:
 
* ‎<code>time</code>‎ هي المدة الزمنية التي ستُجمَع فيها نقاط التعقب بالثواني.
 
* ‎<code>sched</code>‎، و‎<code>gfx</code>‎ ، و‎<code>view</code>‎ هي وسوم android SDK (مجموعات من العلامات)، المهمّ هنا: ‎<code>sched</code>‎ للحصول على معلومات حول ما يُشغَّل على كل نواة (core) من أنوية هاتفك، و‎<code>gfx</code>‎ لمعلومات الرسومات (graphics) مثل حدود الإطارات (frame boundaries)، و‎<code>view</code>‎ يمنحك معلوماتٍ حول القياس والتخطيط ومرور الرسومات (draw passes).
 
*  ‎<code>-a <your_package_name></code>‎  خيارٌ يمكِّن علامات خاصّة بالتطبيق، وتحديدًا تلك المضمنة في إطار React Native.
 
 
 
بمجرد بدء عملية جمع نقاط التعقب، نفّذ على الجهازِ التحريكَ أو التفاعل الذي تريد تعريف أداءه. ستعطيك أداة <code>systrace</code> في نهاية التعقب رابطًا يمكنك فتحه في المتصفّح لتحليل النتائج.
 
 
 
====2. قراءة نقاط التعقب====
 
بعد فتح نتيجة التعقب في متصفحك (ننصح بمتصفح Chrome)، من المفترض أن ترى ما يشابه الصورة التالية:
 
[[ملف:SystraceExample.png|مركز|لاإطار]]
 
 
 
'''تلميح:''' استخدم مفاتيح WASD للتحكم بكيفية العرض والتكبير.
 
 
 
إذا لم يُفتَح ملف HTML الخاص بك بشكل صحيح، انظر وحدة تحكم متصفحك، قد تجد الخطأ التالي:
 
[[ملف:ObjectObserveError.png|مركز|لاإطار]]
 
لأنّ ‎<code>Object.observe</code>‎ قد أصبح مهملًا في المتصفحات الحديثة، فقد تضطر إلى فتح الملف من أداة التعقّب (Tracing tool) في Google Chrome. يمكنك القيام بذلك من خلال:
 
 
 
* فتح علامة تبويب والانتقال إلى العنوان ‎<code>chrome://tracing</code>‎.
 
* تحديد تحميل (load).
 
* تحديد ملف HTML المولَّد من الأمر السابق.
 
 
 
'''تمكين تمييز VSync'''
 
 
 
علّم على مربع الاختيار هذا أعلى الجانب الأيمن من الشاشة لتحديد حدود إطارات 16 مللي ثانية:
 
[[ملف:SystraceHighlightVSync.png|مركز|لاإطار]]
 
من المفترض أن ترى أشرطة تمييز كما في الصورة أعلاه. إذا لم يحدث ذلك، فجرّب تعريف الأداء على جهاز آخر: من الشائع أن تحتوي أجهزة Samsung على مشاكل في عرض أشرطة تمييز VSync بينما تكون أجهزة Nexus موثوقة عمومًا.
 
 
 
====3. البحث عن العملية====
 
 
 
انتقل إلى الأسفل إلى أن تعثر على اسم حزمتك (أو جزءًا من الاسم). في هذه الحالة، كنّا نعرّف أداء الحزمة ‎<code>com.facebook.adsmanager</code>‎ لكن الاسم يظهر على الشكل ‎<code>book.adsmanager</code>‎ بسبب حدود تسمية السلاسل (threads) في النواة.
 
 
 
على الجانب الأيسر، سترى مجموعة من السلاسل التي تتوافق مع صفوف الجدول الزمني على اليمين. هناك عدد قليل من السلاسل المهمّة: سلسلة واجهة المستخدم (التي تُسمى باسم الحزمة أو الاسم UI Thread)، و‎<code>mqt_js</code>‎، و‎<code>mqt_native_modules</code>‎. إذا كنت تستخدم نظام التشغيل Android 5 أو الإصدارات الأحدث، فسلسلة التصيير (Render Thread) مهمّة كذلك.
 
 
 
* سلسلة واجهة المستخدم: هنا تحدث عمليات القياس والتخطيط والرسم فيAndroid. سيكون اسمُ السلسلة على اليمين اسمَ حزمتك (أي ‎<code>book.adsmanager</code>‎ في هذه الحالة) أو الاسم UI Thread. ستبدو الأحداث التي تراها على هذه السلسلة مشابهة لما يلي، وينبغي أن تتعلّق بكلّ من ‎<code>Choreographer</code>‎، و‎<code>traversals</code>‎، و‎<code>DispatchUI</code>‎:
 
[[ملف:SystraceUIThreadExample.png|مركز|لاإطار]]
 
'''سلسلة JS:''' هنا تُنفَّذ JavaScript. سيكون اسم السلسلة إمّا ‎<code>mqt_js</code>‎ أو ‎<code><...></code>‎ حسب مدى تعاون النواة على جهازك. للتعرّف عليها إن لم يكن لها اسم، ابحث عن أمور مثل ‎<code>JSCall</code>‎ أو ‎<code>Bridge.executeJSCall</code>‎، إلخ:
 
[[ملف:SystraceJSThreadExample.png|مركز|لاإطار]]
 
'''سلسلة الوحدات الأصيلة (Native Modules Thread):''' هنا تُنفَّذ استدعاءات الوحدات الأصيلة (كوحدة UIManager). سيكون اسم السلسلة إمّا ‎<code>mqt_native_modules</code>‎ أو ‎<code><...></code>‎. للتعرف عليها في هذه الحالة الأخيرة، ابحث عن أمور مثل ‎<code>NativeCall</code>‎، و‎<code>callJavaModuleMethod</code>‎، و‎<code>onBatchComplete</code>‎:
 
[[ملف:SystraceNativeModulesThreadExample.png|مركز|لاإطار]]
 
سلسلة التصيير (Render Thread): إذا كنت تستخدم نظام التشغيل Android 5 أو الإصدارات الأحدث، فسيحتوي تطبيقك على سلسلة تصييرٍ كذلك. في التطبيق. تولّد هذه السلسلة أوامر OpenGL الفعلية المستخدمة في رسم واجهة المستخدم. سيكون اسم السلسلة إمّا ‎<code>RenderThread</code>‎ أو ‎<code><...></code>‎. للتعرف عليها في هذه الحالة الأخيرة، ابحث عن أمور مثل ‎<code>DrawFrame</code>‎، و‎<code>queueBuffer</code>‎:
 
[[ملف:SystraceRenderThreadExample.png|مركز|لاإطار]]
 
 
 
====تحديد الجاني====
 
ستبدو التحريكات السَّلِسَة والخفيفة كما يلي:
 
[[ملف:SystraceWellBehaved.png|مركز|لاإطار]]
 
كل تغيير في اللون يُمثّل إطارًا واحدًا، تذكّر أنّه لعرض الإطار، يجب إنهاء جميع عمليات واجهة المستخدم بنهاية فترة 16ms. لاحظ عدم وجودٍ أيّ سِلسلةٍ تعمل بالقرب من حدود الإطار. إذا صُيِّر تطبيقٌ بهذا الشكل فالتصيير يسير بمعدّل 60 إطارًا في الثانيّة.
 
 
 
إذا لاحظت تأخّرًا في التحريكات والانتقالات في التطبيق، فقد ترى رسما بيانيًّا كهذا:
 
[[ملف:SystraceBadJS.png|مركز|لاإطار]]
 
لاحظ أن سلسلة JS تُنفَّذ طوال الوقت ولا تقتصر على العمل داخل الفترة الزمنية المحدّدة، وتعمل عبر حدود الأطر! هذا التطبيق لا يُصيّر بمعدّل 60 إطارًا في الثانية. في هذه الحالة، المشكلة في سلسلة JS.
 
 
 
قد ترى أيضًا ما يشبه الصورة التالية:
 
[[ملف:SystraceBadUI.png|مركز|لاإطار]]
 
في هذه الحالة، تكون سلسلة واجهة المستخدم وسلسلة التصيير هما اللتان تتجاوزان حدود الأطر. تتطلب واجهة المستخدم التي نحاول عرضها إنجاز عمل كثير على كل إطار. في هذه الحالة، تكمن المشكلة في العروض الأصيلة التي تُصيَّر.
 
 
 
لديك الآن معلومات مفيدة جدًّا لمساعدتك على اتّخاذ القرارات في الخطوات التالية.
 
 
 
====حل مشاكل JavaScript====
 
إذا عرَّفت مشكلة في JS، فابحث عن أدلّةٍ في شيفرة JavaScript قيد التنفيذ. في السيناريو أعلاه، نلاحظ استدعاء <code>RCTEventEmitter</code> عدة مرات في كل إطار. إليك تكبيرًا لسلسلة JS من التعقّب أعلاه:
 
[[ملف:SystraceBadJS2.png|مركز|لاإطار]]
 
هذا لا يبدو سليمًا. ما سبب الاستدعاءات الكثيرة؟ هل هذه أحداث مختلفة فعلًا؟ قد تعتمد الإجابات على هذه الأسئلة على شيفرة تطبيقك. وفي كثير من الأحيان، سيفيدك إلقاء نظرة على ‎‎<code>shouldComponentUpdate</code>‎‎.
 
 
 
====حل مشاكل واجهة المستخدم الأصيلة (native UI)====
 
إذا عرّفت مشكلة في واجهة المستخدم الأصيلة، فهناك عادة حالتان:
 
 
 
# واجهة المستخدم التي تحاول رسم أطرها تحتاج إلى الكثير من العمل على مستوى وحدة معالجة الرسومات (GPU).
 
#  تطبيقك يُنشئ واجهة مستخدم جديدة أثناء التحريكات أو التفاعلات (كتحميل محتوًى جديدٍ أثناء التمرير [scroll]).
 
 
 
==== الكثير من العمل على وحدة معالجة الرسومات====
 
 
 
في الحالة الأولى، سترى تعقّبًا تظهر فيه سلسلة واجهة المستخدم أو سلسلة التصيير، أو كلاهما، بالشكل التالي:
 
 
 
لاحظ الوقت الطويل الذي يُستغرَق في منطقة <code>DrawFrame</code>، والذي يتخطى حدود الأطر. هذا هو الوقت المستغرق في انتظار وحدة GPU لاستنزاف مخزن الأوامر المؤقت (command buffer) من الإطار السابق.
 
 
 
لتجنّب هذا، ينبغي:
 
* النظر في استخدام ‎‎<code>renderToHardwareTextureAndroid</code>‎‎ للمحتوى الثابت المعقّد الذي يُحرَّك أو يُحوَّل (كتحريكات Alpha في ‎‎<code>Navigator</code>‎‎ مثلا).
 
* تأكد من عدم استخدام ‎‎<code>needsOffscreenAlphaCompositing</code>‎‎، المُعطَّل افتراضيًّا، لأنه يزيد بشكل كبير الحمل لكل إطار على وحدة GPU في معظم الحالات.
 
 
 
إذا لم تساعدك هذه الأمور وأردت التعمق أكثر في عمليات وحدة معالجة الرسومات، انظر [http://developer.android.com/tools/help/gltracer.html Tracer for OpenGL ES].
 
 
 
====إنشاء عروض جديدة على سلسلة واجهة المستخدم====
 
في الحالة الثانية، سترى ما يُشبه التمثيل البياني التالي:
 
[[ملف:SystraceBadCreateUI.png|مركز|لاإطار]]
 
ستلاحظ أن سلسلة JS تتأخّر قليلاً، ثم ستلاحظ إنجاز بعض العمليات على سلسلة الوحدات الأصيلة، ما يُتبَع بعبورٍ مكلّف على سلسلة واجهة المستخدم.
 
 
 
لا توجد طريقة سهلة للحدّ من هذا إلا إذا كنت قادرًا على تأجيل إنشاء واجهة مستخدم جديدة حتى ينتهيَ التفاعل، أو يمكنك تبسيط واجهة المستخدم التي تقوم بإنشائها. يعمل فريق React Native على حلٍّ لهذا الأمر على مستوى البنية التحتية، ما سيتيح إنشاء واجهة مستخدم جديدة وضبطها خارج السلسلة الرئيسية، ما يسمح للتفاعل بالاستمرار بسلاسة.
 
 
 
==حزم RAM واستدعاءات ‎‎<code>require</code>‎‎ على السطر (inline)==
 
إذا كان تطبيقك كبيرًا، فقد تُفيدك وحدات الوصول العشوائي (Random Access Modules) أوRAM اختصارًا، واستخدامُ استدعاءات ‎‎<code>require</code>‎‎ على السطر. هذا مفيد للتطبيقات التي تحتوي على عدد كبير من الشاشات التي قد لا تُفتَح أثناء الاستخدام العادي للتطبيق. عمومًا، هذا مفيدٌ للتطبيقات التي تحتوي على كميات كبيرة من الشيفرة البرمجية غير المطلوبة لفترة من الوقت بعد بدء تشغيل التطبيق. على سبيل المثال، قد يحتوي التطبيق على شاشاتِ ملفات شخصية معقدّة أو ميّزات أقل استخدامًا، لكن معظم الجلسات تتضمن فقط زيارة الشاشة الرئيسية للتطبيق للحصول على التحديثات. يمكننا تحسين تحميل الحزمة باستخدام تنسيق RAM وطلب هذه الميزات والشاشات  على السطر (أي فقط عندما يحتاج إليها المستخدم فعليًا).
 
 
 
=== تحميل JavaScript===
 
قبل أن يُنفِّذ React Native  شفرة JS، يجب تحميل (load) هذه الشيفرة إلى الذاكرة وتحليلها (parse). إذا حمّلت حزمةً قياسيّة بحجم 50mb، فسيتوجّب تحميل كامل الحزمة بحجم 50mb وتحليلها قبل تنفيذ أي منها. يتمثل التحسين في حزم RAM في أنه يُمكنك تحميل الجزء الذي تحتاج إليه فعليًا فقط من حزمة 50 ميغابايت عند بدء التشغيل عوضًا عن تحميلها كلّها، ثم يُحمَّل المزيد من أجزاء الحزمة تدريجيًّا حسب المطلوب.
 
===طلب (أو استيراد) الوحدات والملفات على السطر===
 
تؤخّر الطلبات على السطر استيراد الوحدة أو الملف إلى أن يحتاج إليه التطبيق بالفعل.
 
 
 
مثال بسيط:
 
====<code>'''VeryExpensive.js'''</code>====
 
<syntaxhighlight lang="javascript">
 
import React, { Component } from 'react';
 
import { Text } from 'react-native';
 
// ... استيراد وحدات مكلفة
 
 
 
// قد يُفيدك التسجيل على مستوى الملف للتحقق من أن الاستيراد يحدث بالفعل
 
console.log('VeryExpensive component loaded');
 
 
 
export default class VeryExpensive extends Component {
 
  // الكثير من الشيفرة هنا
 
  render() {
 
    return <Text>Very Expensive Component</Text>;
 
  }
 
}
 
</syntaxhighlight>
 
 
 
هذا تحسين للمكون المكلف أعلاه:
 
====<code>'''Optimized.js'''</code>====
 
<syntaxhighlight lang="javascript">
 
import React, { Component } from 'react';
 
import { TouchableOpacity, View, Text } from 'react-native';
 
 
 
let VeryExpensive = null;
 
 
 
export default class Optimized extends Component {
 
  state = { needsExpensive: false };
 
 
 
  didPress = () => {
 
    if (VeryExpensive == null) {
 
      VeryExpensive = require('./VeryExpensive').default;
 
    }
 
 
 
    this.setState(() => ({
 
      needsExpensive: true,
 
    }));
 
  };
 
 
 
  render() {
 
    return (
 
      <View style={{ marginTop: 20 }}>
 
        <TouchableOpacity onPress={this.didPress}>
 
          <Text>Load</Text>
 
        </TouchableOpacity>
 
        {this.state.needsExpensive ? <VeryExpensive /> : null}
 
      </View>
 
    );
 
  }
 
}
 
</syntaxhighlight>
 
 
 
حتى دون تنسيق RAM، يمكن للاستيرادات على السطر أن تحسّن وقت بدء التشغيل، لأن الشيفرة الموجودة في ملفّ <code>VeryExpensive.js</code> ستنفَّذ فقط عند طلبها لأول مرة.
 
 
 
===تمكين تنسيق RAM===
 
على نظام iOS، سيؤدي استخدام تنسيق RAM إلى إنشاء ملف مفهرس واحد سيُحمَّل من طرف React Native لتحميل وحدة واحدةٍ في كل مرة. على Android، سيُنشئ التنسيق افتراضيًا مجموعة من الملفات لكل وحدة. يمكنك إجبار Android على إنشاء ملف واحد مثل iOS، لكن استخدام ملفات متعددة قد يكون أكثر فاعلية وسيتطلب ذاكرة أقل.
 
 
 
يُمكنك تمكين تنسيق RAM في Xcode عبر تعديل مرحلة البناء "تحزيم شيفرة وصور React Native". أضف السطر ‎‎<code>export BUNDLE_COMMAND="ram-bundle"</code>‎‎ قبل السطر ‎‎<code>../node_modules/react-native/scripts/react-native-xcode.sh</code>‎‎:
 
<syntaxhighlight lang="javascript">
 
export BUNDLE_COMMAND="ram-bundle"
 
export NODE_BINARY=node
 
../node_modules/react-native/scripts/react-native-xcode.sh
 
</syntaxhighlight>
 
 
 
على Android، يمكنك تمكين تنسيق RAM عبر تعديل ملف ‎‎<code>android/app/build.gradle</code>‎‎ الخاص بك. أضف أو عدّل كتلة ‎‎<code>project.ext.react</code>‎‎ قبل السطر ‎‎<code>apply from: "../../node_modules/react-native/react.gradle"</code>‎‎:
 
<syntaxhighlight lang="javascript">
 
project.ext.react = [
 
  bundleCommand: "ram-bundle",
 
]
 
</syntaxhighlight>
 
 
 
استخدم الأسطر التالية على Android إن أردت استخدام ملف واحد مفهرس:
 
<syntaxhighlight lang="javascript">
 
project.ext.react = [
 
  bundleCommand: "ram-bundle",
 
  extraPackagerArgs: ["--indexed-ram-bundle"]
 
]
 
</syntaxhighlight>
 
 
 
===إعداد التحميل القبلي (Preloading) والطلبات على السطر===
 
 
 
الآن بعد أن أصبحت لدينا حزمة RAM، هناك عبء عند استدعاء ‎‎<code>require</code>‎‎. تحتاج ‎‎<code>require</code>‎‎ الآن إلى إرسال رسالة عبر الجسر عندما تجد وحدةً لم تُحمَّل بعد. سيؤثر هذا على بداية التشغيل لأنّها النقطة التي من المرجح أن يحدث فيها أكبر عدد من استدعاءات ‎‎<code>require</code>‎‎ أثناء تحميل التطبيق للوحدة الأولية. لحسن الحظ، يمكننا إعداد جزء من الوحدات لتحميلها قبليًّا. للقيام بهذا، ستحتاج إلى استخدام شكلٍ من أشكال الطلبات على السطر.
 
 
 
===إضافة ملف إعدادات للمحزم===
 
أنشئ مجلدًا في مشروعك باسم ‎‎<code>packager</code>‎‎، وأنشئ ملفًّا واحدًا باسم <code>config.js</code>. ثم أضف ما يلي:
 
 
 
<syntaxhighlight lang="javascript">
 
const config = {
 
  transformer: {
 
    getTransformOptions: () => {
 
      return {
 
        transform: { inlineRequires: true },
 
      };
 
    },
 
  },
 
};
 
 
 
module.exports = config;
 
</syntaxhighlight>
 
 
 
في Xcode، في مرحلة البناء (build phase)، أضف السطر ‎‎<code>export BUNDLE_CONFIG="packager/config.js"</code>‎‎:
 
<syntaxhighlight lang="javascript">
 
export BUNDLE_COMMAND="ram-bundle"
 
export BUNDLE_CONFIG="packager/config.js"
 
export NODE_BINARY=node
 
../node_modules/react-native/scripts/react-native-xcode.sh
 
</syntaxhighlight>
 
 
 
عدّل ملفّ ‎‎<code>android/app/build.gradle</code>‎‎ وأضف ‎‎<code>bundleConfig: "packager/config.js",</code>‎‎.
 
<syntaxhighlight lang="javascript">
 
project.ext.react = [
 
  bundleCommand: "ram-bundle",
 
  bundleConfig: "packager/config.js"
 
]
 
</syntaxhighlight>
 
 
 
 
أخيرًا، يمكنك تحديث ‎‎<code>"start"</code>‎‎ الموجود داخل ‎‎<code>"scripts"</code>‎‎ في ملف package.json الخاص بك لاستخدام الإعداد:
 
<syntaxhighlight lang="javascript">
 
"start": "node node_modules/react-native/local-cli/cli.js start --config ../../../../packager/config.js",
 
</syntaxhighlight>
 
 
 
شغّل خادم الحزمة (package server) الخاص بك بالأمر ‎‎<code>npm start</code>‎‎. لاحظ أنه عند إطلاق برنامج مُحزّم التطوير (dev packager) تلقائيًّا عبر xcode أو الأمر ‎‎<code>react-native run-android</code>‎‎، إلخ، فإنه لا يستخدم الأمر ‎‎<code>npm start</code>‎‎، لذا لن يُستخدَم الإعداد في هذه الحالات.
 
 
 
===التحقيق في الوحدات المحملة===
 
يمكنك في الملف الجذر ‎‎<code>(index.(ios|android).js)</code>‎‎ إضافة ما يلي بعد الاستيرادات الأولية:
 
<syntaxhighlight lang="javascript">
 
const modules = require.getModules();
 
const moduleIds = Object.keys(modules);
 
const loadedModuleNames = moduleIds
 
  .filter(moduleId => modules[moduleId].isInitialized)
 
  .map(moduleId => modules[moduleId].verboseName);
 
const waitingModuleNames = moduleIds
 
  .filter(moduleId => !modules[moduleId].isInitialized)
 
  .map(moduleId => modules[moduleId].verboseName);
 
 
 
// تأكد من أن الوحدات التي تتوقع منها أن تنتظرَ بالفعلِ تنتظرُ
 
console.log(
 
  'loaded:',
 
  loadedModuleNames.length,
 
  'waiting:',
 
  waitingModuleNames.length
 
);
 
 
 
// خُذ هذا المقطع النصيّ وضعه في ملف يُسمى
 
// packager/modulePaths.js
 
console.log(`module.exports = ${JSON.stringify(loadedModuleNames.sort())};`);
 
</syntaxhighlight>
 
 
 
عند تشغيل تطبيقك، يمكنك البحث في الطرفية (console) لمعرفة عدد الوحدات المُحمَّلة وعدد الوحدات قيد الانتظار. قد ترغب بقراءة محتويات ‎‎<code>moduleNames</code>‎‎ للتأكد من عدم وجود أي وحدات غير متوقعة. لاحظ أن الطلبات على السطر تُستدعى في المرة الأولى التي يُشار (referenced) فيها إلى ما استُورِدَ من وحداتٍ. قد تحتاج إلى التحقيق وإعادة تنسيق الشيفرة لضمان أن الوحدات التي تريد تحميلها فقط من سيُحمَّل عند بدء التشغيل. لاحظ أنه يمكنك تغيير الكائن <code>Systrace</code> الموجود في الكائن ‎‎<code>require</code>‎‎ للمساعدة في تصحيح مشاكل الاستيراد.
 
<syntaxhighlight lang="javascript">
 
require.Systrace.beginEvent = (message) => {
 
  if(message.includes(problematicModule)) {
 
    throw new Error();
 
  }
 
}
 
</syntaxhighlight>
 
يختلف كل تطبيق عن الآخر، لكن من المنطقي تحميل الوحدات التي تحتاجها في الشاشة الأولى فقط. إذا رضيت بالنتيجة، ضع مُخرَج ‎‎<code>loadedModuleNames</code>‎‎ في ملفٍ باسم ‎‎<code>packager/modulePaths.js</code>‎‎.
 
 
 
===تحديث ملف <code>config.js</code>===
 
لنعد إلى الملف ‎‎<code>packager/config.js</code>‎‎، يجب تحديثه لاستخدام ملف ‎‎<code>modulePaths.js</code>‎‎ الذي وُلِّدَ حديثًا.
 
<syntaxhighlight lang="javascript">
 
const modulePaths = require('./modulePaths');
 
const resolve = require('path').resolve;
 
const fs = require('fs');
 
 
 
// حدّث السطر التالي إذا كان مجلد جذر تطبيقك في مكان آخر
 
const ROOT_FOLDER = resolve(__dirname, '..');
 
 
 
const config = {
 
  transformer: {
 
    getTransformOptions: () => {
 
      const moduleMap = {};
 
      modulePaths.forEach(path => {
 
        if (fs.existsSync(path)) {
 
          moduleMap[resolve(path)] = true;
 
        }
 
      });
 
      return {
 
        preloadedModules: moduleMap,
 
        transform: { inlineRequires: { blacklist: moduleMap } },
 
      };
 
    },
 
  },
 
  projectRoot:ROOT_FOLDER,
 
};
 
 
 
module.exports = config;
 
</syntaxhighlight>
 
 
 
تُشير خانة ‎‎<code>preloadedModules</code>‎‎ في الإعداد إلى الوحدات التي يجب تعليمها بعلامة التحميل القبليّ عند إنشاء حزمة RAM. عند تحميل الحزمة، ستُحمَّل هذه الوحدات فورًا، حتى قبل أن تُنفّذ استيرادات ‎‎<code>require</code>‎‎. تُشير خانة ‎‎<code>blacklist</code>‎‎ إلى أنّ هذه الوحدات يجب ألا تُطلَب على السطر. وذلك لأنها تُحمَّل قبليًّا، لا فائدةَ من استخدام الطلبات على السطر من ناحية الأداء. في الواقع، تقضي لغة JavaScript وقتًا إضافيًا في حلّ الطلبات على السطر في كل مرة يُشار فيها إلى الوحدات المُستوردة.
 
 
 
===اختبار وقياس التحسينات===
 
يجب أن تكون الآن جاهزًا لبناء تطبيقك باستخدام تنسيق RAM والطلبات على السطر . تيقَّن من قياس وقت التشغيل قبل وبعد استعمال التنسيق.
 
  
 
== مصادر ==
 
== مصادر ==
 
* [https://facebook.github.io/react-native/docs/performance صفحة Performance في توثيق React Native الرسمي.]
 
* [https://facebook.github.io/react-native/docs/performance صفحة Performance في توثيق React Native الرسمي.]
 
[[تصنيف:ReactNative]]
 
[[تصنيف:ReactNative]]
 +
[[تصنيف:React Native Docs]]

المراجعة الحالية بتاريخ 13:44، 9 أكتوبر 2021

أحد الأسباب المقنعة لاستخدام React Native بدلاً من أدواتٍ تعتمد على عارض الويب WebView هو الحصول على تطبيق يعمل بمعدّل 60 إطارًا في الثانية مع شكل ومظهر أصيلٍ. يعمل مطورو React Native على تطوير الإطار ليُنجز المهام بشكل صحيح وفعّال، ليساعدك ذلك على التركيز على تطبيقك بدلاً من تحسين الأداء، ولكن هناك نقصٌ في بعض المناطق، وفي مناطق أخرى لا يُمكن فيها لإطار العمل (كما هو الحال مع كتابة شيفرة باللغة الأصيلة) أن يُحدّد أفضل طريقة للتحسين من أجلك، وبالتالي سيكون التدخل اليدوي ضروريًا. يبذل مطورو إطار العمل قصارى جهدهم لتقديم أداء واجهة مستخدم سلسَةٍ وسريعة افتراضيًّا، لكن أحيانًا لا يكون هذا ممكنًا، لذا عليك أن تُحسّن أداء تطبيقك بنفسك في هذه الحالات.

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

ما تحتاج إلى معرفته حول الإطارات (frames)

وُصِفَت الأفلام بعبارة "الصور المتحركة" لسببٍ واضح: فالحركة الواقعية في مقاطع الفيديو ما هي إلّا وهم يخلقه تغييرٌ سريعٌ للصور الثابتة بسرعة ثابتة. نشير إلى كل صورة من هذه الصور على أنّها إطارٌ واحد. لعددِ الإطارات الذي يُعرَض في كل ثانية تأثيرٌ مباشر على كيفية ظهور الفيديو (أو واجهة المستخدم) من ناحية السلاسة والواقعيّة. تعرض أجهزة iOS واجهة المستخدم بمعدّل60 إطارًا في الثانية، ما يمنحك ونظامَ واجهة المستخدم حوالي 16.67 مللي ثانية للقيام بكل العمل المطلوب لتوليد الصورة الثابتة (الإطار) التي سيراها المستخدم على الشاشة لتلك الفترة الزمنية. إذا لم تقدِر على القيام بالعمل الضروري لتوليد هذا الإطار ضمن الفترة الزمنيّة المخصّصة (16.67ms)، فسوف "تُسقِط إطارًا" وستظهر واجهة المستخدم وكأنّها غير مستجيبة.

لمزيدٍ من الإرباك، افتح قائمة المطورين (developer menu) في تطبيقك وقم بتمكين Show Perf Monitor. ستلاحظ أن هناك معدلي أطرٍ (frame rates) مختلفين.

PerfUtil.png

معدل إطارات JS (سلسلة JavaScript)

في معظم تطبيقات React Native، سيُنفَّذ منطق معاملاتك (business logic) على السلسلة (thread) الخاصّة بلغة JavaScript. هذه هي المنطقة التي يوجد فيها تطبيق React الخاص بك، وهنا تُجرى استدعاءات الواجهات البرمجيّة ومعالجة الأحداث، وما إلى ذلك. تُجمَّع التحديثات التي تتعلّق بالواجهات الأصيلة (native-backed views) وتُرسَل إلى الجانب الأصيل في نهاية كل تكرار من تكرارات حلقة الأحداث (event loop) قبل الموعد النهائي للإطار (هذا إذا سارت الأمور على ما يرام). إذا لم تستجب سلسلة JavaScript لإطارٍ ما، فسَيُعَدُّ إطارًا منسدلًا (dropped frame). على سبيل المثال، إذا استدعيت الدالة this.setState على المكون الجذر لتطبيقٍ معقّد، وأدّى ذلك إلى إعادة تصيير مكوّناتٍ فرعيةٍ تأخذ الكثير من الوقت بسبب إجراء حسابات معقّدة، فمن المرجّح أن هذا قد يستغرق 200 مللي ثانية ويؤدي إلى إسقاط 12 إطارًا. ستظهر أي تحريكاتٍ مُتحّكَمٍ فيها من طرف JavaScript مُجمَّدَةً خلال تلك الفترة. إذا استغرق أي شيء أكثر من 100 مللي ثانية، فسيشعر المستخدم بذلك.

يحدث هذا غالبًا أثناء انتقالات Navigator: عندما تدفعُ موجّهًا (push a route) جديدًا، ستحتاج سلسلة JavaScript إلى تصيير جميع المكونات اللازمة للمشهد لإرسال الأوامر الصحيحة إلى الجانب الأصيل لإنشاء الواجهات الجديدة. من الشائع أن تأخذ هذه العمليّة بعض الإطارات ومن الممكن أن يُسبب ذلك في حدوث ظاهرة jank (أي ظهور التطبيق وكأنه لا يستجيب لتفاعل المستخدم معه) لأن الانتقال مُتحّكَمٌ فيه من طرف سلسلة JavaScript. في بعض الأحيان، تؤدّي المكونات عملًا إضافيًّا في التابع componentDidMount، مما قد يؤدي إلى تأخّرٍ (أو تأتأةٍ: stutter) ثانٍ في الانتقال.

الاستجابة للمسات مثالٌ آخر: إذا كنت تقوم بعملياتٍ عبر عدّة إطارات في سلسلة JavaScript، فقد تلاحظ وجود تأخيرٍ في الاستجابة إلى المكوّن TouchableOpacity مثلًا. ويرجع ذلك إلى أن سلسلة JavaScript مشغولة ولا يمكنها معالجة أحداث اللمس الأولية المرسلة من السلسلة الرئيسيّة. والنتيجة أنّ المكوّن TouchableOpacity غير قادرٍ على التّفاعل مع أحداث اللمس لإخبار الواجهة الأصيلة بتغيير عتامتها (opacity).

معدل إطارات واجهة المستخدم (السلسلة الرئيسية)

لاحظ العديد من الأشخاص أن أداء المكوّن ‎NavigatorIOS‎ أفضلُ من المكوّن ‎Navigator‎ (الذي أُزيل من إطار العمل منذ مُدّة). سبب هذا هو أنّ تحريكات الانتقال تتم كليًّا على السلسلة الرئيسية، ولذلك لا تُقاطَع من طرف الإطارات المنسدلة على سلسلة JavaScript.

وبالمثل، يمكنك التمرير لأعلى ولأسفل في واجهة ScrollView عندما تُقفَل سلسلة JavaScript لأن مكوّن ScrollView موجود في السلسلة الرئيسية. تُرسَل أحداث التمرير (scroll events) إلى سلسلة JavaScript، لكنّ استقبالها ليس ضروريًا ليحدث التمرير.

مصادر شائعة لمشاكل الأداء

تشغيل التطبيق في وضع التطوير (dev=true)

يتأثر أداء سلسلة JavaScript بشدّةٍ عند التشغيل في وضع التطوير. هذا أمرٌ لا مفر منه، إذ يجب القيام بالكثير من العمل في وقت التشغيل (runtime) لتزويدك بتحذيرات ورسائل خطأ جيدة، مثل التحقق من أنواع propTypes ومختلف التأكيدات (assertions) الأخرى. تأكد دائمًا من اختبار الأداء في بناءات الإصدار (release builds).

استخدام جمل console.log

عند تشغيل تطبيق مُجمّع (bundled app)، يمكن أن تتسبب جمل console.log في حدوث خنق كبير في سلسلة JavaScript. وهذا يشمل الاستدعاءات من مكتبات التنقيح مثل redux-logger، لذا تأكد من إزالتها قبل التحزيم. يمكنك كذلك استخدام إضافة babel هذه التي تزيل جميع استدعاءات ‎console.*‎. ستحتاج إلى تثبيتها أولاً باستخدام ‎npm i babel-plugin-transform-remove-console --save-dev‎، ثم عدِّل ملف ‎.babelrc‎ داخل مشروعك على النحو التالي:

{
  "env": {
    "production": {
      "plugins": ["transform-remove-console"]
    }
  }
}

سيؤدي هذا إلى إزالة كافة استدعاءات ‎console.*‎ في نسخ الإصدار (أو الإنتاج [production]) من مشروعك.

تصيير مكون ListView الأولي بطيء جدًا أو أن أداء تمرير القوائم الكبيرة سيئ

استخدم مكون FlatList أو SectionList الجديدين بدلًا من المكون القديم ListView الذي أصبح مُهملًا (deprecated). إلى جانب تبسيط واجهة برمجة التطبيقات (API)، تحتوي مكونات القوائم الجديدة كذلك على تحسيناتِ أداءٍ كبيرة، أكبر هذه التحسينات هو استخدام نفس حجم الذاكرة تقريبًا لأي عدد من الصفوف.

إذا كان تصيير المكوّن FlatList بطيئًا في تطبيقك، فاستعمل الخاصيّة getItemLayout لتحسين سرعة التصيير عبر تخطي قياس العناصر المصيَّرة.

ينخفض معدل إطارات JS في الثانيّة (JS FPS) عند إعادة تصيير مكون لا يتغير تقريبا

إذا كنت تستخدم مكوّن ListView، عليك توفير دالة rowHasChanged التي يمكن لها أن تختصر الكثير من الجهد بتحديد ما إذا كان الصف يحتاج إلى إعادة تصييره سريعًا. إذا كنت تستخدم هياكل بيانات غير قابلة للتغيير (immutable data structures)، فسيكون هذا بسيطًا عبر التحقق من المساواة المرجعية (reference equality).

وبالمثل، يمكنك استخدام shouldComponentUpdate والإشارة إلى الشروط الدقيقة التي ترغب بإعادة تصيير المكون بها. إذا كنت تكتب مكونات نقية (حيث تعتمد القيمة المعادة [return value] لدالة التصيير [render function] اعتمادًا كاملًا على الخاصيات والحالة)، يمكنك الاستفادة من المكون PureComponent الصرف للقيام بذلك نيابة عنك. مجدّدًا، هياكل البيانات غير القابلة للتغيير مفيدة للحفاظ على سرعة الأداء، إذا توجَّب عليك القيام بمقارنةٍ عميقةٍ (deep comparison) لقائمة كبيرة من الكائنات، فقد تكون إعادة تصيير المكون بأكمله أسرع، وسيتطلب ذلك شيفرةً أقل بالتأكيد.

سقوط إطارات على سلسلة JS بسبب إجراء عمليات كثيرة على سلسلة JS في نفس الوقت

التنقّل البطيء في المتنقّل (Navigator) من المظاهر الشائعة لهذه المشكلة، لكن هناك حالات أخرى يحدث فيها هذا. يمكن لاستخدام واجهة InteractionManager البرمجيّة أن يكون طريقة جيدة لحلّها، لكن إذا كان تأخير تحريكٍ مُكلّفًا على تجربة المستخدم. فانظر واجهة LayoutAnimation البرمجية.

تحسب واجهة Animated حاليًا كل إطارٍ مفتاحيّ (keyframe) حسب الطلب (on-demand) على سلسلة JavaScript، إلا إذا قمت بضبط useNativeDriver: true، في حين أن واجهة LayoutAnimation تعتمد على التحريكات الأساسية (Core Animation) ولا تتأثر بسقوط الإطارات في سلسلة JS والسلسلة الرئيسية.

هذه حالة يُمكن فيها استخدام هذه الطريقة: التحريك في مربّع حوار (التمرير لأسفل من أعلى مع التلاشي في تراكبٍ نصف شفاف) أثناء التهيئة وربما تلقّي استجابات لطلباتٍ متعددة من الشبكة، وتقديم محتويات النافذة، وكذا تحديث العرض الذي فُتحت منه النافذة. انظر دليل "التحريكات" لمزيد من المعلومات حول كيفية استخدام واجهة LayoutAnimation.

تحذير: لا يعمل LayoutAnimation إلا مع التحريكات الساكنة (‎"static"‎ animations‎)، أي تلك التي تبدأ وتستمر في العمل دون حاجة إلى تغييرها. إذا لزِمَت مقاطعة التحريكات، فستحتاج إلى استخدام واجهة ‎Animated‎.

نقل واجهة على الشاشة (التمرير، الترجمة، التدوير) يسقط معدل الإطارات على سلسلة واجهة المستخدم

يحدث هذا بشكل خاص عندما يكون لديك نص بخلفية شفّافة موضوعٍ فوق صورة، أو أي حالة أخرى تتطلب تركيب ألفا (alpha compositing) لإعادة رسم الواجهة على كل إطار. قد تجد أنّ تمكينَ ‎shouldRasterizeIOS‎ أو ‎renderToHardwareTextureAndroid‎ يساعدُ بشكل ملحوظ.

اِحرص على عدم الإفراط في استخدام هذه الحيلة، فقد تستنزف ذاكرة الهاتف. افحص حالة الأداءِ وحالة استخدامِ الذاكرة عند الاعتماد على هذه الخاصيات. إذا كنت تُخطّط لإيقاف تحريك الواجهة، فعطّل هذه الخاصية.

يسقط تحريك حجم صورة معدل الإطارات على سلسلة واجهة المستخدم

على iOS، في كل مرة تغيّر فيها ضبط عرض أو ارتفاع مكوّن Image، يُعاد اقتصاصها وتحجيمها من الصورة الأصلية. قد يكون هذا مكلفًا جدا، خاصّة للصور الكبيرة. بدلاً من هذا، اِستخدم خاصية النمط ‎transform: [{scale}]‎ لتحريك الحجم. النقر فوق صورة وتكبيرها إلى وضع ملء الشاشة من الأمثلة على حالة من حالات الاعتماد على هذه الخاصية.

واجهة ‎TouchableX‎ لا تستجيب بسرعة

أحيانًا، إذا أجرينا عمليّة في نفس الإطار الذي نُعدّل فيه العتامة (opacity) أو الإبراز (highlight) لأحد المكونات التي تستجيب إلى اللمس، فلن نرى هذا التأثير إلا بعد أن تُعيد الدالة onPress. إذا كانت الدالة onPress تغيّر الحالة بالدالة setState، ما يحتاج إلى عمليات مكلّفة ويسبّب سقوط بعض الإطارات، فقد يبدو المكون القابل للمس غير مستجيب. أحد حلول هذه المشكلة هو تغطية أي عمليات داخل معالج الدالة onPress بالدالة requestAnimationFrame كما يلي:

handleOnPress() {
  requestAnimationFrame(() => {
    this.doExpensiveAction();
  });
}

انتقالات بطيئة في مكون Navigator

كما ذُكِر أعلاه، يُتحكَّمُ في تحريكات Navigator بواسطة سلسلة JavaScript. تخيل مشهد الانتقال "من اليمين إلى اليسار": يُنقَل المشهد الجديد في كل إطار من اليمين إلى اليسار، بدءًا من خارج الشاشة (عند نقطة x-offset تساوي 320 مثلًا) إلى أن يستقّر في النهاية عند نقطة x-offset تساوي 1.

في كل إطار أثناء هذا النقل، ستحتاج سلسلة JavaScript إلى إرسال نقطة x-offset جديدة إلى السلسلة الرئيسية. إذا كانت سلسلة JavaScript مُقفلَةً (locked up)، فلا يمكن القيام بهذا، وبالتالي لا يحدث أي تحديث على ذلك الإطار وستتأخّر التحريكات.

أحد الحلول لهذه المشكلة هو السماح للتحريكات المعتمدة على JavaScript بنقل تحميلها (offloaded) إلى السلسلة الرئيسية. لو كتبنا نفس المثال أعلاه بهذا الأسلوب، فمن الممكن مثلا أن نحسب قائمة بجميع نقاط x-offsets للمشهد الجديد عندما نبدأ الانتقال، ثمّ نرسلها إلى السلسلة الرئيسية لتنفيذها بطريقة محسنة. الآن بعد تحرير سلسلة JavaScript من هذه المسؤولية، لن يكون سقوط بعض الإطارات أثناء تصيير المشهد مشكلة كبيرة. قد لا تلاحظ ذلك حتى، لأنّ الانتقال الجميل سيُشتّت انتباهك.

حل هذه المشكلة أحد الأهداف الرئيسية لمكتبة React Navigation الجديدة. تَستخدِم الواجهات في React Navigation مكوناتٍ أصيلة ومكتبة Animated لتقديم تحريكاتٍ بمعدّل 60 إطارًا في الثانية تُشغَّل على السلسلة الأصيلة.

مصادر