الفرق بين المراجعتين لصفحة: «ReactNative/performance»

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


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


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


==الوصول إلى قائمة المطوّرين داخل التطبيق (In-App Developer Menu)==


يمكنك الوصول إلى قائمة المطورين عبر هزّ الهاتف (أو رجِّه) أو عن طريق تحديد "Shake Gesture" داخل قائمة Hardware في محاكي iOS. يمكنك كذلك استخدام اختصار لوحة المفاتيح  ‎<code>⌘D</code> عند تشغيل التطبيق في محاكي iOS، أو ‎<code>⌘M</code> عند تشغيله في محاكي Android على نظام التشغيل Mac OS و <code>Ctrl+M</code> على Windows و Linux. وكبديلٍ آخر في نظام التشغيل Android، يمكنك تنفيذ الأمر ‎<code>adb shell input keyevent 82</code>‎ لفتح قائمة المطورين (82 هو رمز مفتاح القائمة).
لمزيدٍ من الإرباك، افتح قائمة المطورين (developer menu) في تطبيقك وقم بتمكين Show Perf Monitor. ستلاحظ أن هناك معدلي أطرٍ (frame rates) مختلفين.
[[ملف:DeveloperMenu.png|مركز|لاإطار]]
'''ملاحظة:''' قائمة المطورين معطّلة في بناءات الإصدارات (أو بناءات الإنتاج: production builds).


== إعادة تحميل JavaScript==
[PerfUtil.png]
بدلاً من إعادة تجميع (recompiling) تطبيقك في كل مرة تجري فيها تغييرًا، يمكنك إعادة تحميل شيفرة JavaScript لتطبيقك فورًا. للقيام بذلك، حدد "Reload" من قائمة المطورين. يمكنك أيضًا الضغط على ‎<code>⌘R</code> في محاكي iOS أو النقر فوق <code>R</code> مرتين على محاكيات Android.


===إعادة التحميل المباشر===
يمكنك تسريع وقت التطوير من خلال إعادة تحميل تطبيقك تلقائيًا في أي وقت تتغير فيه الشيفرة. يمكن تمكين إعادة التحميل المباشر عن طريق تحديد "Enable Live Reload" من قائمة المطورين.


يمكنك حتى الحفاظ على التطبيق مُشغّلًا أثناء قدوم نسخٍ جديدة من ملفاتك وحقنها تلقائيا داخل حزمة JavaScript عبر تمكين إعادة التحميل الساخن [https://facebook.github.io/react-native/blog/2016/03/24/introducing-hot-reloading.html Hot Reloading] من قائمة المطورين. سيتيح لك هذا إبقاء حالة التطبيق كما هي حتى بعد إعادة التحميل.
=== معدل إطارات 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 new route) جديدًا، ستحتاج سلسلة JavaScript إلى تصيير جميع المكونات اللازمة للمشهد لإرسال الأوامر الصحيحة إلى الجانب الأصيل لإنشاء العروض الجديدة. من الشائع أن تأخذ هذه العمليّة هنا بعض الإطارات ومن الممكن أن يُسبب ذلك في حدوث ظاهرة jank لأن الانتقال مُتحّكَمٌ فيه من طرف سلسلة JavaScript. في بعض الأحيان، تؤدّي المكونات عملًا إضافيًّا في التابعcomponentDidMount، مما قد يؤدي إلى تأرجحٍ (stutter) ثانٍ في الانتقال.


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


* عند إضافة موارد (resources) جديدة إلى حزمة تطبيقك الأصيل، كصورةٍ في <code>Images.xcassets</code> على iOS أو مجلد ‎<code>res/drawable</code>‎ على Android.
=== معدل إطارات واجهة المستخدم (السلسلة الرئيسية)===
* عند تعديل شيفرةٍ أصيلة (Objective-C أو Swift على iOS أو Java أو C++‎ على Android).
لاحَظَ العديد من الأشخاص أن أداء المكوّن ‎<code>NavigatorIOS</code>‎ أفضلُ من المكوّن ‎<code>Navigator</code>‎ (الذي أُزيل من إطار العمل منذ مُدّة). سبب هذا هو أنّ تحريكات الانتقال تتم كليًّا على السلسلة الرئيسية، ولذلك لا تُقاطَع من طرف الإطارات المنسدلة على سلسلة JavaScript.


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


===الأخطاء===
==مصادر شائعة لمشاكل الأداء==
تُعرَض الأخطاء داخل التطبيق في نافذة تنبيه تملؤ الشاشة مع خلفية حمراء داخل تطبيقك. تُعرف هذه الشاشة باسم الصندوق الأحمر <code>RedBox</code>. يمكنك استخدام‎<code>console.error()</code> ‎ لإطلاقها يدويا.


===التحذيرات===
===تشغيل التطبيق في وضع التطوير (dev=true)===
تُعرَض التحذيرات داخل التطبيق في نافذة تملؤ الشاشة مع خلفية صفراء داخل تطبيقك. تُعرف هذه الشاشة باسم الصندوق الأصفر <code>YellowBox</code>. انقر على التنبيهات لعرض المزيد من المعلومات أو لإغلاقها.


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


يمكن تعطيل الصناديق الصفراء أثناء التطوير باستخدام ‎<code>console.disableYellowBox = true;</code>‎ يمكن تجاهل تحذيراتٍ محدّدة برمجيًا عن طريق تعيين مصفوفة من السابقات (prefixes) التي يجب تجاهلها:
=== استخدام جمل console.log ===
 
عند تشغيل تطبيق مُحزَّم (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">
import {YellowBox} from 'react-native';
{
YellowBox.ignoreWarnings(['Warning: ...']);
  "env": {
    "production": {
      "plugins": ["transform-remove-console"]
    }
  }
}
</syntaxhighlight>
</syntaxhighlight>
سيؤدي هذا إلى إزالة كافة استدعاءات ‎<code>console.*</code>‎ في نسخ الإصدار (أو الإنتاج [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، إلا إذا قمت بضبط ‎<code>useNativeDriver: true</code>‎، في حين أن واجهة LayoutAnimation يعتمد على التحريكات الأساسية (Core Animation) ولا يتأثر بسقوط الإطارات في سلسلة JS والسلسلة الرئيسية.
هذه حالة يُمكن فيها استخدام هذه الطريقة: التحريك في مربّع حوار (التمرير لأسفل من أعلى مع التلاشي في تراكبٍ نصف شفاف) أثناء التهيئة وربما تلقي استجابات لطلباتٍ متعددة من الشبكة، وتقديم محتويات النافذة، وكذا تحديث العرض الذي فُتحت منه النافذة. انظر دليل "التحريكات" لمزيد من المعلومات حول كيفية استخدام واجهة LayoutAnimation.


في CI أو Xcode، يمكن أيضًا تعطيل الصناديق الصفراء عن طريق تعيين متغير البيئة <code>IS_TESTING</code>.


'''ملاحظة:''' تُعطَّلُ الصناديق الصفراء والحمراء تلقائيًا في بناءات الإصدارات (بناءات الإنتاج).
محاذير:
* لا يعمل LayoutAnimation إلا مع التحريكات الساكنة (‎"static"‎ animations‎)، أي تلك التي تبدأ وتستمر في العمل دون حاجة إلى تغييرها. إذا لزِمَت مقاطعة التحريكات، فستحتاج إلى استخدام واجهة ‎<code>Animated</code>‎.


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


لتنقيح شيفرة JavaScript في Chrome، حدّد "Debug JS Remotely" من قائمة المطورين. سيفتح هذا علامة تبويب جديدة تشير إلى العنوان ‎<code>http://localhost:8081/debugger-ui</code>‎.
اِحرص على عدم الإفراط في استخدام هذه الحيلة، فقد تستنزف ذاكرة الهاتف. افحص حالة الأداءِ وحالة استخدامِ الذاكرة عند الاعتماد على هذه الخاصيات. إذا كنت تُخطّط لإيقاف تحريك العرض، فعطّل هذه الخاصية.


حدّد ‎<code>Tools → Developer Tools</code>‎ من قائمة Chrome لفتح [https://developer.chrome.com/devtools أدوات المطور]. يمكنك كذلك الوصول إلى أدوات المطور باستخدام اختصارات لوحة المفاتيح (‎<code>⌘⌥I</code>‎ على macOS و ‎<code>Ctrl+Shift+I</code>‎ في Windows). قد ترغب كذلك بتمكين خيار "[https://stackoverflow.com/questions/2233339/javascript-is-there-a-way-to-get-chrome-to-break-on-all-errors/17324511#17324511 Pause On Caught Exceptions]" للحصول على تجربة تنقيح أفضل.
===يسقط تحريك حجم صورة معدل الإطارات على سلسلة واجهة المستخدم===
على iOS، في كل مرة تغيّر فيها ضبط عرض أو ارتفاع مكوّن Image، يُعاد اقتصاصها وتحجيمها من الصورة الأصلية. قد يكون هذا مكلفًا جدا، خاصّة للصور الكبيرة. بدلاً من هذا، اِستخدم خاصية النمط ‎<code>transform: [{scale}]</code>‎ لتحريك الحجم. النقر فوق صورة وتكبيرها إلى وضع ملء الشاشة من الأمثلة على حالة من حالات الاعتماد على هذه الخاصية.


'''ملاحظة:''' لا تعمل إضافة React Developer Tools مع React Native، لكن يمكنك استخدام الإصدار المستقل منها (standalone version) بدلاً من الإضافة. انظر هذا القسم لكيفيّة القيام بهذا.
===عرض ‎<code>TouchableX</code>‎ لا يستجيب بسرعة===
أحيانًا، إذا أجرينا عمليّة في نفس الإطار الذي نُعدّل فيه العتامة (opacity) أو الإبراز (highlight) لأحد المكونات التي تستجيب إلى اللمس، فلن نرى هذا التأثير إلا بعد أن تُعيد الدالة onPress. إذا كانت الدالة onPress تغيّر الحالة بالدالة setState ما يحتاج إلى عمليات مكلّفة ويسبّب سقوط بعض الإطارات، فقد يبدو المكون القابل لللمس غير مستجيب. أحد حلول هذه المشكلة هو تغطية أي عمليات داخل معالج الدالة onPress الخاص بك بالدالة requestAnimationFrame كما يلي (لكن استخدم دائمًا مخلوط TimerMixin مع كل من requestAnimationFrame، وsetTimeout، وsetInterval، انظر صفحة المؤقتات):
<syntaxhighlight lang="javascript">
handleOnPress() {
  // Always use TimerMixin with requestAnimationFrame, setTimeout and
  // setInterval
  this.requestAnimationFrame(() => {
    this.doExpensiveAction();
  });
}
</syntaxhighlight>


===تصحيح الأخطاء باستخدام منقح JavaScript مخصص===
=== انتقالات بطيئة في مكون Navigator===
لاستخدام منقح JavaScript مخصص بدلاً من أدوات مطوري Chrome، اضبط متغيرَ البيئة <code>REACT_DEBUGGER</code> ليشير إلى أمرٍ يُشغِّل المنقّح المخصص. يمكنك بعد ذلك تحديد "Debug JS Remotely" من قائمة المطورين لبدء التنقيح.
كما ذُكِر أعلاه، يُتحكَّمُ في تحريكات Navigator بواسطة سلسلة JavaScript. تخيل مشهد الانتقال "من اليمين إلى اليسار": يُنقَل المشهد الجديد في كل إطار من اليمين إلى اليسار، بدءًا من خارج الشاشة (عند نقطة x-offset تساوي 320 مثلًا) إلى أن يستقّر في النهاية عند نقطة x-offset  تساوي 1.


سيستقبل المنقح قائمةً بكافة جذور المشروع، مفصولة بمسافة. على سبيل المثال، إذا ضبطتَ المتغيِّر على الشكل ‎<code>REACT_DEBUGGER="node /path/to/launchDebugger.js --port 2345 --type ReactNative"</code>‎ فهذا يعني أنّ الأمر ‎<code>node /path/to/launchDebugger.js --port 2345 --type ReactNative /path/to/reactNative/app</code>‎ هو الذي سيُستخدَم لتشغيل المنقح الخاص بك.
في كل إطار أثناء هذا النقل، ستحتاج سلسلة JavaScript إلى إرسال نقطة x-offset جديدة إلى السلسلة الرئيسية. إذا كانت سلسلة JavaScript مُقفلَةً (locked up)، فلا يمكن القيام بهذا، وبالتالي لا يحدث أي تحديث على ذلك الإطار وستتأخّر التحريكات.


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


==أدوات مطوري Safari==
حل هذه المشكلة أحد الأهداف الرئيسية لمكتبة [https://facebook.github.io/react-native/docs/navigation React Navigation] الجديدة. تَستخدِم العروض في React Navigation مكوناتٍ أصيلة ومكتبة Animated لتقديم تحريكاتٍ بمعدّل 60 إطارًا في الثانية تُشغَّل على السلسلة الأصيلة.
يمكنك استخدام متصفح Safari لتنقيح نسخة iOS من تطبيقك دون الحاجة إلى تمكين خيار "Debug JS Remotely".


* مكِّن قائمة المطورين في Safari: عبر ‎<code>Preferences → Advanced → "Show Develop menu in menu bar"</code>‎
==تعريف الأداء (Profiling)==
* حدد سياق <code>JSContext</code> الخاص بتطبيقك: عبر ‎<code>Develop → Simulator → JSContext</code>‎
استخدم أداة التعريف (profiler) المُضَمَّنَة للحصول على معلومات مفصّلة حول العمل المنجز في سلسلة JavaScript والسلسلة الرئيسية جنبًا إلى جنب. يمكن الوصول إلى الأداة عن طريق تحديد Perf Monitor من قائمة Debug.
* سيُفتَح مفتش الويب (Web Inspector) الخاص بمتصفّح Safari الذي يحتوي على لوحة تحكّم (Console) ومنقّح (Debugger).


لكن لهذه الطريقة بعض العيوب:
في iOS، أداةُ Instruments أداةٌ لا تقدر بثمن، أمّا في نظام Android، عليك أن تتعلم كيفيّة استخدام systrace.


* لا توجد خرائط مصدرٍ (sourcemaps) عند تصحيح الأخطاء.
لكن أولًا، تأكّد من أن وضع التطوير (Development Mode) مُعطَّل، يجب أن ترى العبارة ‎<code>__DEV__ === false, development-level warning are OFF, performance optimizations are ON</code>‎ في سجلات تطبيقك.
* في كل مرة يُعاد فيها تحميل التطبيق (باستخدام إعادة التحميل المباشر، أو بإعادة التحميل يدويًا)، يُنشأُ سياقُ <code>JSContext</code> جديد. يسمح لك خيار "Automatically Show Web Inspectors for JSContexts" بتجنّب الاضطرار إلى تحديد أحدث سياق <code>JSContext</code> يدويًا.


==أدوات تطوير React==
هناك طريقة أخرى لتعريف الأداء في JavaScript، وهي استخدام أداة تعريف Chrome أثناء تصحيح الأخطاء. لن يمنحك هذا نتائج دقيقة لأنّ الشيفرة تُشغّل في Chrome، ولكنه سيعطيك فكرة عامة عن مواضع الاختناقات التي تعيق الأداء. شغِّل أداة Profiler ضمن علامة التبويب "Performance" في Chrome. سوف يظهر رسم بياني تحت عنوان ‎<code>User Timing</code>‎. لعرض مزيد من التفاصيل في جدول، انقر فوق علامة التبويب ‎<code>Bottom Up</code>‎ في الأسفل، ثم حدد "DedicatedWorker Thread" في أعلى القائمة اليسرى.


يمكنك استخدام [https://github.com/facebook/react-devtools/tree/master/packages/react-devtools الإصدار المستقل من أدوات تطوير React] لتنقيح تسلسل مكونات React الهرميّ. من أجل هذا، ثبِّت حزمة  <code>react-devtools</code> تثبيتا عامًّا (globally):
=== تعريف أداء واجهة المستخدم في Android بأداة Systrace ===
<syntaxhighlight lang="bash">
يدعم Android آلاف الهواتف المختلفة وهو نظام مُعمَّم لدعم التصيير البرمجي (software rendering): بنية الإطار والحاجة إلى التعميم عبر العديد من الأجهزة المستهدفة يعني أنّك تحصل على مميّزات أقل مقارنة بنظام iOS. لكن أحيانا هناك أمور يمكن تحسينها - وفي أحيان كثيرة لا يكون الخطأ في الشيفرة الأصيلة على الإطلاق!
npm install -g react-devtools
</syntaxhighlight>


شغّل الآن <code>react-devtools</code> من الطرفية لتشغيل تطبيق DevTools المستقل:
الخطوة الأولى لتصحيح تأخر الإطارات هي الإجابة عن السؤال الأساسي:ما الذي يفعله التطبيق في فترة 16ms في كل إطار. للإجابة على هذا السؤال، سنستخدم أداة Android قياسية لتعريف الأداء تسمى systrace.
<syntaxhighlight lang="bash">
react-devtools
</syntaxhighlight>


[[ملف:ReactDevTools.png|مركز|لاإطار]]
systrace أداةُ تعريف قياسية في Android تعتمد على العلامات (marker-based) وتُثبَّت عند تثبيت حزمة أدوات (platform-tools) نظام Android. تحاط كُتَل الشيفرة المُعرَّفة (Profiled code blocks)  بعلامات بدءٍ (start) ونهاية (end) والتي تُمثَّل في مخطط بيانيّ ملون. يوفر كل من Android SDK و React Native علامات قياسية (standard markers) يمكن تمثيلها.


سيتصل التطبيق بجهاز المحاكاة الخاص بك في غضون ثوان قليلة.
==== 1. جمع نقاط التعقب (trace)====
أولاً، قم بتوصيل جهازٍ تظهر فيه مشكلة تأخر الإطارات التي تريد التحقيق فيها مع حاسوبك عبر USB وانتقل في التطبيق إلى مكان التحريك أو الانتقال الذي تريد تعريف أدائه. ثمّ شغّل أداة systrace على النحو التالي:
<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>react-devtools</code>‎ كاعتماديّة (dependency) من اعتماديات المشروع. أضف حزمة ‎<code>react-devtools</code>‎ إلى مشروعك باستخدام الأمر ‎<code>npm install --save-dev react-devtools</code>‎، ثم أضف ‎<code>"react-devtools": "react-devtools"</code>‎ إلى قسم  ‎<code>scripts</code>‎ في ملفّ package.json، ثم نفّذ الأمر ‎<code>npm run react-devtools</code>‎ من مجلد المشروع الخاص بك لفتح أدوات DevTools.
شرح سريع لهذا الأمر:
* ‎<code>time</code>‎ هي المدة الزمنية التي ستُجمَع فيها نقاط التعقب بالثواني.
* ‎<code>time</code>‎، و‎<code>time</code>‎ ، و‎<code>time</code>‎ هي وسوم android SDK (مجموعات من العلامات)، المهمّ هنا: ‎<code>sched</code>‎ للحصول على معلومات حول ما يُشغَّل على كل نواة (core) من أنوية هاتفك، و‎<code>gfx</code>لمعلومات الرسومات (graphics) مثل حدود الإطارات (frame boundaries)، و‎<code>view</code>‎ يمنحك معلوماتٍ حول القياس والتخطيط ومرور الرسومات (draw passes).


===الدمج مع مفتش React Native===
*  ‎<code>-a <your_package_name></code>‎  خيارٌ يمكِّن علامات خاصّة بالتطبيق، وتحديدًا تلك المضمنة في إطار React Native.
افتح قائمة المطوّرين داخل التطبيق واختر "Toggle Inspector". سيظهر لك تراكبٌ (overlay) يتيح لك النقر على أي عنصر من عناصر واجهة المستخدم والاطلاع على معلومات حوله:


[Inspector.gif]
بمجرد بدء عملية جمع نقاط التعقب، نفّذ على الجهازِ التحريكَ أو التفاعل الذي تريد تعريف أداءه. ستعطيك أداة systrace في نهاية التعقب رابطًا يمكنك فتحه في المتصفّح لتحليل النتائج.


لكن أثناء تشغيل ‎<code>react-devtools</code>‎، سيدخل المفتش في وضعِ طيٍّ (collapsed mode) خاص، وسيستخدم أداة DevTools كواجهة مستخدم أساسية. في هذا الوضع، سيؤدي النقر على جزءٍ ما من أجزاء التطبيق في جهاز المحاكاة إلى إظهار المكونات ذات الصلة في أدوات DevTools:
====2. قراءة نقاط التعقب====
[ReactDevToolsInspector.gif]


يمكنك اختيار "Toggle Inspector" في نفس القائمة للخروج من هذا الوضع.
بعد فتح نتيجة التعقب في متصفحك (ننصح بمتصفح Chrome)، من المفترض أن ترى ما يشابه الصورة التالية:


===تفتيش نسخ المكون (Component Instances)===
[SystraceExample.png]
عند تنقيح JavaScript في متصفّح Chrome، يمكنك تفحّص الخاصيّات وحالة مكونات React في وحدة تحكم المتصفح (browser console).


أولاً ، اِتّبع إرشادات التنقيح في Chrome لفتح وحدةِ تحكمِ متصفّحِ Chrome.
تلميح: استخدم مفاتيح WASD للتحكم بكيفية العرض والتكبير.


تأكد من أن القائمة المنسدلة في الجانب العلوي الأيسر من وحدة تحكم Chrome تحتوي على اسم debuggerWorker.js. هذه الخطوة ضرورية.
إذا لم يُفتَح ملف html الخاص بك بشكل صحيح، انظر وحدة تحكم متصفحك، قد تجد الخطأ التالي:


بعدها حدّد مكون React في أدوات React DevTools. هناك مربع بحث في الأعلى سيساعدك على العثور على مكوّنٍ باسمه. بمجرد تحديد المكون، سيكون متاحًا عبر المتغيّر ‎<code>$r</code>‎ في وحدة تحكم Chrome، مما يسمح لك بفحص خاصيّاته والحالة وخاصيات النسخة.
[ObjectObserveError.png]
[ReactDevToolsDollarR.gif]
لأنّ ‎<code>Object.observe</code>‎ قد أصبح مهملًا في المتصفحات الحديثة، فقد تضطر إلى فتح الملف من أداة التعقّب (Tracing tool) في Google Chrome. يمكنك القيام بذلك من خلال:


==مراقبة الأداء==
* فتح علامة تبويب والانتقال إلى العنوان ‎<code>chrome://tracing</code>‎.
يمكنك تمكين شاشة تراكب (overlay) لمراقبة أداء التطبيق لمساعدتك على إصلاح مشاكل الأداء عن طريق تحديد "Perf Monitor" في قائمة المطورين.
* تحديد تحميل (load).
==تنقيح التطبيقات المُخرجَة (Ejected Apps)==
* تحديد ملف html المولَّد من الأمر السابق.


===المشاريع ذات شيفرة أصيلة فقط===
تمكين تمييز VSync
ما تبقى من هذا الدليل ينطبق فقط على  المشاريع التي بُدِأَت باستخدام الأمر ‎<code>react-native init</code>‎ أو على المشاريع المبدوءة بالأمر ‎<code>expo init</code>‎ أو أداة Create React Native App التي أُخرِجت عبر الأمر ‎<code>eject</code>‎. لمزيد من المعلومات حول عمليّة الإخراج، انظر [https://github.com/react-community/create-react-native-app/blob/master/EJECTING.md هذا الدليل الموجود في مستودع أداة Create React Native App].


==الوصول إلى سجلات وحدة التحكم (console logs)==
علّم على مربع الاختيار هذا أعلى الجانب الأيمن من الشاشة لتحديد حدود إطارات 16 مللي ثانية:
يمكنك عرض سجلات وحدة التحكم لتطبيق iOS أو Android باستخدام الأوامر التالية في طرفيّةٍ أثناء تشغيل التطبيق:


<syntaxhighlight lang="bash">
[SystraceHighlightVSync.png]
$ react-native log-ios
$ react-native log-android
</syntaxhighlight>
يمكنك كذلك الوصول إلى هذه الملفات عبر ‎<code>Debug → Open System Log...</code>‎ في محاكي iOS أو عن طريق تنفيذ الأمر ‎<code>adb logcat *:S ReactNative:V ReactNativeJS:V</code>‎ في الطرفيّة أثناء تشغيل تطبيق Android على جهازٍ أو محاكٍ.


* إذا كنت تستخدم أداة Create React Native App أو Expo CLI، فستظهر السّجلات تلقائيًّا في نفس الطرفيّة التي تحتوي على مُخرَجات المُحزّم.
من المفترض أن ترى أشرطة تمييز كما في الصورة أعلاه. إذا لم يحدث ذلك، فجرّب تعريف الأداء على جهاز آخر: من الشائع في أجهزة Samsung أن تحتوي على مشاكل في عرض أشرطة تمييز VSync بينما تكون أجهزة Nexus موثوقة عمومًا.


==التنقيح على جهاز باستخدام أدوات مطوري Chrome==
====3. البحث عن العملية====
'''ملاحظة:''' إذا كنت تستخدم Create React Native App أو Expo CLI، فالإعدادات مضبوطة مسبقًا، لذا لا حاجة لاتباع هذه الخطوات.


على أجهزة iOS، افتح الملف [https://github.com/facebook/react-native/blob/master/Libraries/WebSocket/RCTWebSocketExecutor.m RCTWebSocketExecutor.m] وغيّر "localhost" إلى عنوان IP الخاص بحاسوبك، ثم حدد "Debug JS Remotely" من قائمة المطورين.
انتقل إلى الأسفل إلى أن تعثر على اسم حزمتك (جزءًا من الاسم). في هذه الحالة، كنا نعرّف أداء الحزمة ‎<code>com.facebook.adsmanager</code>‎ لكن الاسم يظهر على الشكل ‎<code>book.adsmanager</code>‎ بسبب حدود تسمية السلاسل (threads) في النواة.


على أجهزة Android 5.0+‎ المتصلة عبر USB، يمكنك استخدام [http://developer.android.com/tools/help/adb.html أداة سطر الأوامر adb] لإعداد توجيه المنفذ (port forwarding) من الجهاز إلى حاسوبك:
على الجانب الأيسر، سترى مجموعة من السلاسل التي تتوافق مع صفوف الجدول الزمني على اليمين. هناك عدد قليل من السلاسل المهمّة: سلسلة واجهة المستخدم (التي تُسمى باسم الحزمة أو الاسم UI Thread)، و‎<code>mqt_js</code>‎، و‎<code>mqt_native_modules</code>‎. إذا كنت تستخدم نظام التشغيل Android 5 أو الإصدارات الأحدث، فسلسلة التصيير (Render Thread) مهمّة كذلك.
<syntaxhighlight lang="javascript">
adb reverse tcp:8081 tcp:8081
</syntaxhighlight>


كبديل، حدّد "Dev Settings" من قائمة المطورين، ثم حدِّث إعداد "Debug server host for device" ليُطابِق عنوان IP الخاص بحاسوبك.
* سلسلة واجهة المستخدم: هنا تحدث عمليات القياس والتخطيط والرسم فيAndroid. سيكون اسمُ السلسلة على اليمين اسمَ حزمتك (أي ‎<code>book.adsmanager</code>‎ في هذه الحالة) أو الاسم UI Thread. ستبدو الأحداث التي تراها على هذه السلسلة مشابهة لما يلي، وينبغي أن تتعلّق بكلّ من ‎<code>Choreographer</code>‎، و‎<code>traversals</code>‎، و‎<code>DispatchUI</code>‎:


* إذا واجهتك أي مشاكل، فمن الممكن أنّ أحد إضافات Chrome الخاصة بك تتفاعل مع المنقّح بطرق غير متوقعة. جرِّب تعطيل جميع الإضافات وإعادة تمكينها واحدة تلو الأخرى حتى تعثر على الإضافة المعطوبة.
[SystraceUIThreadExample.png]
=== تصحيح الأخطاء باستخدام Stetho على Android===


اتبع هذا الدليل لتمكين [http://facebook.github.io/stetho/ Stetho] في وضع التنقيح (Debug mode):
سلسلة JS: هنا تُنفَّذ JavaScript. سيكون اسم السلسلة إمّا ‎<code>mqt_js</code>‎ أو ‎<code><...></code>‎ حسب مدى تعاون النواة على جهازك. للتعرّف عليها إن لم يكن لها اسم، ابحث عن أمور مثل ‎<code>JSCall</code>‎ أو ‎<code>Bridge.executeJSCall</code>‎، إلخ:


* 1: أضف الأسطر التاليّة في قسم ‎<code>android/app/build.gradle</code>‎ في ملف ‎<code>dependencies</code>‎:
[SystraceJSThreadExample.png]
<syntaxhighlight lang="javascript">
debugCompile 'com.facebook.stetho:stetho:1.5.0'
debugCompile 'com.facebook.stetho:stetho-okhttp3:1.5.0'
</syntaxhighlight>
'''ملاحظة:''' ستضبط الشيفرة أعلاه Stetho v1.5.0. انظر [http://facebook.github.io/stetho/ صفحة المشروع] لترى ما إذا توفر إصدار أحدث.
* 2: أنشئ أصناف Java التالية لتغليف (wrap) استدعاء Stetho، واحدٌ للإصدار (release) وآخر من أجل التنقيح:
<syntaxhighlight lang="java">
// android/app/src/release/java/com/{yourAppName}/StethoWrapper.java


public class StethoWrapper {
سلسلة الوحدات الأصيلة (Native Modules Thread): هنا تُنفَّذ استدعاءات الوحدات الأصيلة (كوحدة UIManager). سيكون اسم السلسلة إمّا ‎<code>mqt_native_modules</code>‎ أو ‎<code><...></code>‎. للتعرف عليها في هذه الحالة الأخيرة، ابحث عن أمور مثل ‎<code>NativeCall</code>‎، و‎<code>callJavaModuleMethod</code>‎، و‎<code>onBatchComplete</code>‎:


    public static void initialize(Context context) {
[SystraceNativeModulesThreadExample.png]
        // NO_OP
    }


    public static void addInterceptor() {
سلسلة التصيير (Render Thread): إذا كنت تستخدم نظام التشغيل Android 5 أو الإصدارات الأحدث، فسيحتوي تطبيقك على سلسلة تصييرٍ كذلك. في التطبيق. تولّد هذه السلسلة أوامر OpenGL الفعلية المستخدمة في رسم واجهة المستخدم. سيكون اسم السلسلة إمّا ‎<code>RenderThread</code>‎ أو ‎<code><...></code>‎. للتعرف عليها في هذه الحالة الأخيرة، ابحث عن أمور مثل ‎<code>DrawFrame</code>‎، و‎<code>queueBuffer</code>‎:
        // NO_OP
    }
}
</syntaxhighlight>


<syntaxhighlight lang="java">
[SystraceRenderThreadExample.png]
// android/app/src/debug/java/com/{yourAppName}/StethoWrapper.java


public class StethoWrapper {
====تحديد الجاني====
    public static void initialize(Context context) {
ستبدو التحريكات السَّلِسَة والخفيفة كما يلي:
      Stetho.initializeWithDefaults(context);
    }


    public static void addInterceptor() {
[SystraceWellBehaved.png]
      final OkHttpClient baseClient = OkHttpClientProvider.createClient();
      OkHttpClientProvider.setOkHttpClientFactory(new OkHttpClientFactory() {
        @Override
        public OkHttpClient createNewNetworkModuleClient() {
          return baseClient.newBuilder()
              .addNetworkInterceptor(new StethoInterceptor())
              .build();
        }
      });
    }
}
</syntaxhighlight>


* 3: افتح ‎<code>android/app/src/main/java/com/{yourAppName}/MainApplication.java</code>‎ واستبدل دالّة onCreate الأصليّة:
كل تغيير في اللون يُمثّل إطارًا واحدًا، تذكّر أنّه لعرض الإطار، يجب إنهاء جميع عمليات واجهة المستخدم بنهاية فترة 16ms. لاحظ عدم وجودٍ أيّ سِلسلةٍ تعمل بالقرب من حدود الإطار. إذا صُيِّر تطبيقٌ بهذا الشكل فالتصيير يسير بمعدّل 60 إطارًا في الثانيّة.
<syntaxhighlight lang="java">
  public void onCreate() {
      super.onCreate();


      if (BuildConfig.DEBUG) {
إذا لاحظت تأخّرًا في التحريكات والانتقالات في التطبيق، فقد ترى رسما بيانيًّا كهذا:
          StethoWrapper.initialize(this);
[SystraceBadJS.png]
          StethoWrapper.addInterceptor();
لاحظ أن سلسلة JS يتم تُنفَّذ طوال الوقت ولا تقتصر على العمل داخل الفترة الزمنية المحدّدة، وتعمل عبر حدود الأطر! هذا التطبيق لا يُصيّر بمعدّل 60 إطارًا في الثانية. في هذه الحالة، المشكلة تكمن في سلسلة JS.
      }


      SoLoader.init(this, /* native exopackage */ false);
قد ترى أيضًا ما يشبه الصورة التالية:
    }
[SystraceBadUI.png]
</syntaxhighlight>
* 4: افتح المشروع في Android Studio وحل أي مشكلات تتعلق بالاعتماديات. سترشدك بيئة التطوير (IDE) أثناء تنفيذ هذه الخطوات بعد تحريك المؤشر فوق الخطوط الحمراء.


* 5: نفّذ الأمر ‎<code>react-native run-android</code>‎.
في هذه الحالة، تكون سلسلة واجهة المستخدم وسلسلة التصيير هما اللتان تتجاوزان حدود الأطر. تتطلب واجهة المستخدم التي نحاول عرضها إنجاز عمل كثير على كل إطار. في هذه الحالة، تكمن المشكلة في العروض الأصيلة التي تُصيَّر.


* 6: افتح علامة تبويب جديدة في متصفّح Chrome، ثمّ افتح العنوان ‎<code>chrome://inspect</code>‎، ثم انقر على عنصر "Inspect device" الموجود بجوار جملة "Powered by Stetho".
لديك الآن معلومات مفيدة جدًّا لمساعدتك على اتّخاذ القرارات في الخطوات التالية.


==تنقيح الشيفرة الأصيلة==
عند التطوير باستخدام الشيفرة الأصيلة، ككتابة وحداتٍ أصيلة مثلًا، يمكنك تشغيل التطبيق من Android Studio أو Xcode والاستفادة من ميّزات التنقيح التي توفرها هذه الأدوات الأصيلة (مثل إعداد نقاط توقفٍ (breakpoints) وما إلى ذلك)، بنفس الطريقة التي قد تتبّعها في حالة إنشاء تطبيق أصيل عاديّ.
== مصادر ==
== مصادر ==
* [https://facebook.github.io/react-native/docs/debugging صفحة Debugging في توثيق React Native الرسمي.]
* [https://facebook.github.io/react-native/docs/performance صفحة Performance في توثيق React Native الرسمي.]
[[تصنيف:ReactNative]]
[[تصنيف:ReactNative]]

مراجعة 18:44، 7 فبراير 2019

الأداء

أحد الأسباب المقنعة لاستخدام 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 new 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 كما يلي (لكن استخدم دائمًا مخلوط TimerMixin مع كل من requestAnimationFrame، وsetTimeout، وsetInterval، انظر صفحة المؤقتات):

handleOnPress() {
  // Always use TimerMixin with requestAnimationFrame, setTimeout and
  // setInterval
  this.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 إطارًا في الثانية تُشغَّل على السلسلة الأصيلة.

تعريف الأداء (Profiling)

استخدم أداة التعريف (profiler) المُضَمَّنَة للحصول على معلومات مفصّلة حول العمل المنجز في سلسلة JavaScript والسلسلة الرئيسية جنبًا إلى جنب. يمكن الوصول إلى الأداة عن طريق تحديد Perf Monitor من قائمة Debug.

في iOS، أداةُ Instruments أداةٌ لا تقدر بثمن، أمّا في نظام Android، عليك أن تتعلم كيفيّة استخدام systrace.

لكن أولًا، تأكّد من أن وضع التطوير (Development Mode) مُعطَّل، يجب أن ترى العبارة ‎__DEV__ === false, development-level warning are OFF, performance optimizations are ON‎ في سجلات تطبيقك.

هناك طريقة أخرى لتعريف الأداء في JavaScript، وهي استخدام أداة تعريف Chrome أثناء تصحيح الأخطاء. لن يمنحك هذا نتائج دقيقة لأنّ الشيفرة تُشغّل في Chrome، ولكنه سيعطيك فكرة عامة عن مواضع الاختناقات التي تعيق الأداء. شغِّل أداة Profiler ضمن علامة التبويب "Performance" في Chrome. سوف يظهر رسم بياني تحت عنوان ‎User Timing‎. لعرض مزيد من التفاصيل في جدول، انقر فوق علامة التبويب ‎Bottom Up‎ في الأسفل، ثم حدد "DedicatedWorker Thread" في أعلى القائمة اليسرى.

تعريف أداء واجهة المستخدم في Android بأداة Systrace

يدعم Android آلاف الهواتف المختلفة وهو نظام مُعمَّم لدعم التصيير البرمجي (software rendering): بنية الإطار والحاجة إلى التعميم عبر العديد من الأجهزة المستهدفة يعني أنّك تحصل على مميّزات أقل مقارنة بنظام iOS. لكن أحيانا هناك أمور يمكن تحسينها - وفي أحيان كثيرة لا يكون الخطأ في الشيفرة الأصيلة على الإطلاق!

الخطوة الأولى لتصحيح تأخر الإطارات هي الإجابة عن السؤال الأساسي:ما الذي يفعله التطبيق في فترة 16ms في كل إطار. للإجابة على هذا السؤال، سنستخدم أداة Android قياسية لتعريف الأداء تسمى systrace.

systrace أداةُ تعريف قياسية في Android تعتمد على العلامات (marker-based) وتُثبَّت عند تثبيت حزمة أدوات (platform-tools) نظام Android. تحاط كُتَل الشيفرة المُعرَّفة (Profiled code blocks) بعلامات بدءٍ (start) ونهاية (end) والتي تُمثَّل في مخطط بيانيّ ملون. يوفر كل من Android SDK و React Native علامات قياسية (standard markers) يمكن تمثيلها.

1. جمع نقاط التعقب (trace)

أولاً، قم بتوصيل جهازٍ تظهر فيه مشكلة تأخر الإطارات التي تريد التحقيق فيها مع حاسوبك عبر USB وانتقل في التطبيق إلى مكان التحريك أو الانتقال الذي تريد تعريف أدائه. ثمّ شغّل أداة systrace على النحو التالي:

$ <path_to_android_sdk>/platform-tools/systrace/systrace.py --time=10 -o trace.html sched gfx view -a <your_package_name>

استبدل ‎<path_to_android_sdk>‎ بمسار Android SDK في حاسبوك و‎<your_package_name>‎ باسم حزمة التطبيق، يُمكنك العثور على هذا الاسم في ملفّ ‎AndroidManifest.xml‎ الخاص بتطبيقك، ويبدو على الشكل ‎com.example.app‎.

شرح سريع لهذا الأمر:

  • time‎ هي المدة الزمنية التي ستُجمَع فيها نقاط التعقب بالثواني.
  • time‎، و‎time‎ ، و‎time‎ هي وسوم android SDK (مجموعات من العلامات)، المهمّ هنا: ‎sched‎ للحصول على معلومات حول ما يُشغَّل على كل نواة (core) من أنوية هاتفك، و‎gfx‎ لمعلومات الرسومات (graphics) مثل حدود الإطارات (frame boundaries)، و‎view‎ يمنحك معلوماتٍ حول القياس والتخطيط ومرور الرسومات (draw passes).
  • -a <your_package_name>‎ خيارٌ يمكِّن علامات خاصّة بالتطبيق، وتحديدًا تلك المضمنة في إطار React Native.

بمجرد بدء عملية جمع نقاط التعقب، نفّذ على الجهازِ التحريكَ أو التفاعل الذي تريد تعريف أداءه. ستعطيك أداة systrace في نهاية التعقب رابطًا يمكنك فتحه في المتصفّح لتحليل النتائج.

2. قراءة نقاط التعقب

بعد فتح نتيجة التعقب في متصفحك (ننصح بمتصفح Chrome)، من المفترض أن ترى ما يشابه الصورة التالية:

[SystraceExample.png]

تلميح: استخدم مفاتيح WASD للتحكم بكيفية العرض والتكبير.

إذا لم يُفتَح ملف html الخاص بك بشكل صحيح، انظر وحدة تحكم متصفحك، قد تجد الخطأ التالي:

[ObjectObserveError.png] لأنّ ‎Object.observe‎ قد أصبح مهملًا في المتصفحات الحديثة، فقد تضطر إلى فتح الملف من أداة التعقّب (Tracing tool) في Google Chrome. يمكنك القيام بذلك من خلال:

  • فتح علامة تبويب والانتقال إلى العنوان ‎chrome://tracing‎.
  • تحديد تحميل (load).
  • تحديد ملف html المولَّد من الأمر السابق.

تمكين تمييز VSync

علّم على مربع الاختيار هذا أعلى الجانب الأيمن من الشاشة لتحديد حدود إطارات 16 مللي ثانية:

[SystraceHighlightVSync.png]

من المفترض أن ترى أشرطة تمييز كما في الصورة أعلاه. إذا لم يحدث ذلك، فجرّب تعريف الأداء على جهاز آخر: من الشائع في أجهزة Samsung أن تحتوي على مشاكل في عرض أشرطة تمييز VSync بينما تكون أجهزة Nexus موثوقة عمومًا.

3. البحث عن العملية

انتقل إلى الأسفل إلى أن تعثر على اسم حزمتك (جزءًا من الاسم). في هذه الحالة، كنا نعرّف أداء الحزمة ‎com.facebook.adsmanager‎ لكن الاسم يظهر على الشكل ‎book.adsmanager‎ بسبب حدود تسمية السلاسل (threads) في النواة.

على الجانب الأيسر، سترى مجموعة من السلاسل التي تتوافق مع صفوف الجدول الزمني على اليمين. هناك عدد قليل من السلاسل المهمّة: سلسلة واجهة المستخدم (التي تُسمى باسم الحزمة أو الاسم UI Thread)، و‎mqt_js‎، و‎mqt_native_modules‎. إذا كنت تستخدم نظام التشغيل Android 5 أو الإصدارات الأحدث، فسلسلة التصيير (Render Thread) مهمّة كذلك.

  • سلسلة واجهة المستخدم: هنا تحدث عمليات القياس والتخطيط والرسم فيAndroid. سيكون اسمُ السلسلة على اليمين اسمَ حزمتك (أي ‎book.adsmanager‎ في هذه الحالة) أو الاسم UI Thread. ستبدو الأحداث التي تراها على هذه السلسلة مشابهة لما يلي، وينبغي أن تتعلّق بكلّ من ‎Choreographer‎، و‎traversals‎، و‎DispatchUI‎:

[SystraceUIThreadExample.png]

سلسلة JS: هنا تُنفَّذ JavaScript. سيكون اسم السلسلة إمّا ‎mqt_js‎ أو ‎<...>‎ حسب مدى تعاون النواة على جهازك. للتعرّف عليها إن لم يكن لها اسم، ابحث عن أمور مثل ‎JSCall‎ أو ‎Bridge.executeJSCall‎، إلخ:

[SystraceJSThreadExample.png]

سلسلة الوحدات الأصيلة (Native Modules Thread): هنا تُنفَّذ استدعاءات الوحدات الأصيلة (كوحدة UIManager). سيكون اسم السلسلة إمّا ‎mqt_native_modules‎ أو ‎<...>‎. للتعرف عليها في هذه الحالة الأخيرة، ابحث عن أمور مثل ‎NativeCall‎، و‎callJavaModuleMethod‎، و‎onBatchComplete‎:

[SystraceNativeModulesThreadExample.png]

سلسلة التصيير (Render Thread): إذا كنت تستخدم نظام التشغيل Android 5 أو الإصدارات الأحدث، فسيحتوي تطبيقك على سلسلة تصييرٍ كذلك. في التطبيق. تولّد هذه السلسلة أوامر OpenGL الفعلية المستخدمة في رسم واجهة المستخدم. سيكون اسم السلسلة إمّا ‎RenderThread‎ أو ‎<...>‎. للتعرف عليها في هذه الحالة الأخيرة، ابحث عن أمور مثل ‎DrawFrame‎، و‎queueBuffer‎:

[SystraceRenderThreadExample.png]

تحديد الجاني

ستبدو التحريكات السَّلِسَة والخفيفة كما يلي:

[SystraceWellBehaved.png]

كل تغيير في اللون يُمثّل إطارًا واحدًا، تذكّر أنّه لعرض الإطار، يجب إنهاء جميع عمليات واجهة المستخدم بنهاية فترة 16ms. لاحظ عدم وجودٍ أيّ سِلسلةٍ تعمل بالقرب من حدود الإطار. إذا صُيِّر تطبيقٌ بهذا الشكل فالتصيير يسير بمعدّل 60 إطارًا في الثانيّة.

إذا لاحظت تأخّرًا في التحريكات والانتقالات في التطبيق، فقد ترى رسما بيانيًّا كهذا: [SystraceBadJS.png] لاحظ أن سلسلة JS يتم تُنفَّذ طوال الوقت ولا تقتصر على العمل داخل الفترة الزمنية المحدّدة، وتعمل عبر حدود الأطر! هذا التطبيق لا يُصيّر بمعدّل 60 إطارًا في الثانية. في هذه الحالة، المشكلة تكمن في سلسلة JS.

قد ترى أيضًا ما يشبه الصورة التالية: [SystraceBadUI.png]

في هذه الحالة، تكون سلسلة واجهة المستخدم وسلسلة التصيير هما اللتان تتجاوزان حدود الأطر. تتطلب واجهة المستخدم التي نحاول عرضها إنجاز عمل كثير على كل إطار. في هذه الحالة، تكمن المشكلة في العروض الأصيلة التي تُصيَّر.

لديك الآن معلومات مفيدة جدًّا لمساعدتك على اتّخاذ القرارات في الخطوات التالية.

مصادر