{"id":"l-rn-5-1","courseId":"react-native-apps","lang":"ar","kind":"lesson","summary":"اعثر على أخطاء Trailhead وشاشاته البطيئة وأصلحها باستخدام LogBox وReact Native DevTools وPerformance Monitor والـ Profiler، مع القياس في وضع الإنتاج قبل تغيير الكود.","html":"<p>هناك خلل ما في Trailhead: السجلّ يتمرّر بقفزات، وزرّ ما أحيانًا لا يفعل شيئًا، ومرة في الأسبوع يُغلَق التطبيق وحده لدى أحد المستخدمين. تصحيح الأخطاء على الجوّال يتبع العادات نفسها التي على الويب: أعِد إنتاج المشكلة، ولاحظ، وكوّن فرضية، وقِس. الأدوات مختلفة، وواحدة منها، أي build الإصدار، أهمّ مما يتوقّعه الناس.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الأدوات التي لديك</h2>\n<p><strong>LogBox</strong> هي الطبقة الظاهرة داخل التطبيق. الخطأ الأحمر بملء الشاشة استثناء JavaScript لم يُلتقط، مع stack trace يشير إلى مصدر الكود. والتحذيرات تظهر شريطًا صغيرًا في الأسفل. أصلح التحذيرات فور ظهورها؛ ومن العادات الجيدة في الفرق أن يُعامَل أي تحذير جديد كأنه اختبار فاشل، لأن التحذيرات التي تتجاهلها هي نفسها التي تتحوّل إلى انهيارات.</p>\n<p>مخرجات <strong><code dir=\"ltr\">console.log</code></strong> تظهر في الطرفية التي تشغّل Metro وفي console الـ DevTools. جيدة للفحوص السريعة؛ لكن أزلها من المسارات الساخنة مثل <code dir=\"ltr\">renderItem</code>، حيث يبطئ تسجيل كل صفّ القائمةَ نفسها.</p>\n<p><strong>React Native DevTools</strong> هو أداة التصحيح المدمجة، مبني على Chrome DevTools. اضغط <code dir=\"ltr\">j</code> في طرفية Metro، أو اختر Open DevTools من قائمة التطوير (<code dir=\"ltr\">m</code> في الطرفية، أو هزّ الجهاز). وفيه:</p>\n<ul>\n<li><strong>Console</strong> متصل بتطبيقك العامل،</li>\n<li><strong>Sources</strong> مع نقاط التوقّف والتنفيذ خطوة بخطوة وتعابير المراقبة (<code dir=\"ltr\">Cmd+P</code> / <code dir=\"ltr\">Ctrl+P</code> للبحث عن ملف)،</li>\n<li>مستكشف <strong>Components</strong>، لرؤية شجرة الـ components والـ props والـ state والـ hooks،</li>\n<li>الـ <strong>Profiler</strong>، لتسجيل عمليات الـ render ومعرفة ما الذي رُسم ولماذا.</li>\n</ul>\n<p>نقطة توقّف واحدة أفضل من عشرة استدعاءات <code dir=\"ltr\">console.log</code> حين لا تعرف أين الخطأ. ضع واحدة في معالج الزر الذي «أحيانًا لا يفعل شيئًا»، وانقر عليه، وافحص الـ state التي لديك فعلًا.</p>\n<p><strong>الانهيارات الأصلية</strong> (native crashes)، حين يُغلَق التطبيق دون شاشة حمراء، تأتي من الكود الأصلي (مكتبة، أو إذن مضبوط بشكل خاطئ، أو نفاد الذاكرة). أدوات JavaScript لا تراها. اقرأ سجلّات الجهاز: console في Xcode لـ iOS، وLogcat في Android Studio، أو مخرجات الطرفية من <code dir=\"ltr\">npx expo run:android</code>. وفي بيئة الإنتاج، تجمع خدمة تقارير أخطاء مثل Sentry انهيارات JavaScript والانهيارات الأصلية معًا من المستخدمين الحقيقيين، مع stack traces.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">قِس في وضع الإنتاج</h2>\n<p>يشغّل وضع التطوير فحوصًا إضافية: التحقّق من الـ props، والتحذيرات، وقائمة التطوير، وFast Refresh. ويمكن أن يجعل JavaScript أبطأ بعدّة أضعاف. الحكم على الأداء هناك يشبه توقيت عدّاء يحمل حقيبة ظهر. قبل أن تغيّر أي كود من أجل السرعة، شغّل التطبيق دون وضع التطوير:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">npx</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> expo</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> start</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --no-dev</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --minify</span></span></code></pre></div>\n<p>أو ثبّت build إصدار (ستصنع واحدًا باستخدام EAS لاحقًا في هذا القسم). اختبر على هاتف Android متوسط الفئة؛ فإن كان سلسًا هناك، فهو سلس في معظم الأماكن.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ JS thread أم الـ UI thread؟</h2>\n<p>افتح قائمة التطوير وفعّل <strong>Performance Monitor</strong>. يعرض معدّلَي إطارات، ويقابلان الـ threads الاثنين من الدرس الأول تمامًا:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>العَرَض</th>\n<th>السبب المرجّح</th>\n<th>أين تبحث</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>ينخفض JS FPS ويبقى UI FPS جيدًا</td>\n<td>JavaScript أكثر من اللازم: عمليات re-render، وعمل ثقيل في الـ render، وتحليل JSON كبير</td>\n<td>الـ Profiler والـ components</td>\n</tr>\n<tr>\n<td>ينخفض UI FPS</td>\n<td>عمل أصلي أكثر من اللازم: أشجار views عميقة، وصور ضخمة، وظلال كثيرة</td>\n<td>تسلسل الـ views وأحجام الصور</td>\n</tr>\n<tr>\n<td>النقرات تبدو متأخّرة</td>\n<td>الـ JS thread مشغول حين تصل اللمسة</td>\n<td>الـ Profiler أثناء التفاعل</td>\n</tr>\n</tbody>\n</table></div><p>معظم المشكلات في تطبيقات React Native على جانب JavaScript، ومعظم هذه سببها <strong>إعادة الـ render أكثر من اللازم</strong>. سجّل جلسة Profiler بينما تعيد إنتاج البطء، ثم ابحث عن components تُرسم أكثر بكثير مما ينبغي. ويستطيع الـ Profiler تسجيل سبب رسم كل component (props تغيّرت، أو state، أو context، أو الأب)، وهذا يحوّل التخمين إلى إصلاح.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الإصلاحات، بترتيب العائد</h2>\n<p>راجع أولًا قائمة التحقّق من درس القوائم: صفوف مغلّفة بـ memo، وprops ثابتة، و<code dir=\"ltr\">keyExtractor</code>، وصور بالحجم المناسب. وأبعد من القوائم:</p>\n<ul>\n<li><strong>انقل العمل خارج الـ render.</strong> ترتيب 600 رحلة أو تنسيق التواريخ في كل render يتراكم. احسبه مرة واحدة باستخدام <code dir=\"ltr\">useMemo</code>، أو حين تتغيّر البيانات.</li>\n<li><strong>لا تُعِد رسم العالم على مؤقّت.</strong> ساعة تدقّ أو موقع GPS حيّ مخزّن في state شاشة كبيرة يعيد الـ render لكل ما تحته. ضع الـ state سريعة التغيّر في أصغر component يعرضها.</li>\n<li><strong>حرّك على الـ UI thread.</strong> واجهة <code dir=\"ltr\">Animated</code> مع <code dir=\"ltr\">useNativeDriver: true</code>، أو مكتبة Reanimated، تشغّل الحركات أصليًا، فلا يستطيع JS thread مشغول أن يجعلها تتقطّع. والإيماءات المقترنة بالحركات (السحب للحذف، والأوراق السفلية) مكانها هناك أيضًا.</li>\n<li><strong>دع المُترجِم يتولّى الـ memoization.</strong> يدعم Expo الـ React Compiler، الذي يُدرج الـ memoization تلقائيًا. إنه يُلغي فئة كاملة من <code dir=\"ltr\">memo</code> و<code dir=\"ltr\">useCallback</code> المكتوبة يدويًا، لكنه لا يصلح العمل الثقيل داخل الـ render.</li>\n</ul>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>تحسين ما لم تقِسه</span></p>\n<p>رشّ <code dir=\"ltr\">useCallback</code> و<code dir=\"ltr\">memo</code> في أنحاء الكود «من أجل الأداء» يجعل الكود أصعب قراءةً ولا يغيّر شيئًا في أحيان كثيرة، لأن الكلفة الحقيقية كانت في مكان آخر: صورة بعرض 4000 بكسل، أو مؤقّت يعيد رسم الشاشة، أو ببساطة وضع التطوير. قِس في وضع الإنتاج، واعثر على النقطة الساخنة، وأصلحها، ثم قِس مرة أخرى.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">روتين لتصحيح الأخطاء يعمل</h2>\n<p>حين يصل بلاغ عن خطأ، دوّن الخطوات بالضبط، والجهاز، وإصدار نظام التشغيل. أعِد إنتاج الخطأ على جهاز مشابه. حدّد في أي جانب يقع (خطأ JavaScript، أو انهيار أصلي، أو أداء). استخدم الأداة المناسبة. وحين تصلحه، اسأل نفسك: هل كان يمكن لاختبار أن يلتقطه؟ وهذا موضوع الدرس التالي.</p>\n","toc":[{"id":"sec-1","text":"الأدوات التي لديك","level":2},{"id":"sec-2","text":"قِس في وضع الإنتاج","level":2},{"id":"sec-3","text":"الـ JS thread أم الـ UI thread؟","level":2},{"id":"sec-4","text":"الإصلاحات، بترتيب العائد","level":2},{"id":"sec-5","text":"روتين لتصحيح الأخطاء يعمل","level":2}],"takeaways":["اضغط <code dir=\"ltr\">j</code> في طرفية Metro (أو استخدم قائمة التطوير) لفتح React Native DevTools، وفيها console ونقاط توقّف (breakpoints) ومستكشف Components وProfiler.","وضع التطوير أبطأ بكثير من build الإصدار، فاحكم على الأداء باستخدام <code dir=\"ltr\">npx expo start --no-dev --minify</code> أو build حقيقي.","يعرض Performance Monitor معدّلَي الإطارات لـ JS وUI منفصلين؛ وأيهما ينخفض يخبرك أين المشكلة.","معظم البطء في JavaScript داخل React Native سببه عمليات re-render أكثر من اللازم وبتكرار أكثر من اللازم؛ ويُظهر الـ Profiler أي components ولماذا.","شغّل الحركات على الـ UI thread (عبر Reanimated، أو Animated مع الـ native driver) حتى لا يستطيع JS thread مشغول أن يجعلها تتقطّع."],"further":[{"title":"React Native DevTools","url":"https://reactnative.dev/docs/react-native-devtools"},{"title":"Debugging Basics","url":"https://reactnative.dev/docs/debugging"},{"title":"Performance Overview","url":"https://reactnative.dev/docs/performance"}],"quiz":[{"q":"<p>تتمرّر قائمة الرحلات في Trailhead بشكل سيئ في وضع التطوير على هاتفك. ماذا يجب أن تفعل قبل تحسين أي شيء؟</p>","options":[{"text":"غلّف كل component بـ <code dir=\"ltr\">memo</code>.","why":"الـ memoization العشوائي يضيف تعقيدًا وقد لا يمسّ المشكلة الحقيقية. قِس أولًا."},{"text":"حوّل القائمة من FlatList إلى ScrollView.","why":"هذا يرسم كل صف مقدّمًا، فيجعل القوائم الطويلة أبطأ لا أسرع."},{"text":"اشترِ أحدث هاتف متاح واختبر عليه.","why":"مستخدموك لا يملكون أحدث هاتف. اختبر على جهاز متوسط الفئة، وفي وضع الإنتاج."},{"text":"شغّل JavaScript في وضع الإنتاج (<code dir=\"ltr\">npx expo start --no-dev --minify</code> أو build إصدار) وتحقّق هل ما زال بطيئًا.","why":"صحيح. يضيف وضع التطوير فحوصًا وتحذيرات تبطئ كل شيء؛ وكثير من «أخطاء الأداء» تختفي في وضع الإنتاج."}],"answer":3},{"q":"<p>يُظهر Performance Monitor الـ UI thread عند 60 FPS والـ JS thread ينخفض إلى 15 FPS أثناء التمرير. أين المشكلة؟</p>","options":[{"text":"في JavaScript، كأن تُعاد عمليات render للـ components مع كل حدث تمرير، أو أن يكون هناك عمل ثقيل داخل الـ render.","why":"صحيح. الجانب الأصلي يواكب؛ أما JavaScript لديك فلا. وسيُظهر الـ Profiler أي components مشغولة."},{"text":"في الـ native views، كأن تكون هناك Views متداخلة كثيرة جدًا أو صور كبيرة جدًا.","why":"كان ذلك سيظهر انخفاضًا في الـ UI thread. وهنا الـ UI thread بخير."},{"text":"في الشبكة، لأن الطلبات تحجب الـ JS thread.","why":"طلبات الشبكة غير متزامنة؛ وانتظارها لا يستهلك إطارات الـ JS thread."}],"answer":0},{"q":"<p>حركة ظهور تدريجي في شاشة تفاصيل الرحلة تتقطّع بينما تحمّل الشاشة بياناتها. ما الإصلاح الأمتن؟</p>","options":[{"text":"أخّر تحميل البيانات حتى تنتهي الحركة.","why":"هذا يبطئ الشاشة لإخفاء التقطّع، وأي عمل آخر في JavaScript قد يسبّبه مع ذلك."},{"text":"شغّل الحركة على الـ UI thread، باستخدام Reanimated أو <code dir=\"ltr\">Animated</code> مع <code dir=\"ltr\">useNativeDriver: true</code>.","why":"صحيح. الحركات التي تُقاد على الـ UI thread لا تعتمد على الـ JS thread المشغول، فتبقى سلسة."},{"text":"قلّل مدّة الحركة ليصبح التقطّع أقصر.","why":"التقطّع الأقصر يبقى تقطّعًا؛ والسبب أن الـ JS thread مشغول."}],"answer":1}],"exercise":{"kind":"spot-bug","language":"jsx","code":"import { memo, useEffect, useState } from 'react';\nimport { FlatList, Text, View } from 'react-native';\nimport { useHikes } from '../state/hikes';\n\nconst HikeRow = memo(function HikeRow({ hike }) {\n  return (\n    <View style={{ padding: 12 }}>\n      <Text>{hike.name}</Text>\n      <Text>{hike.distanceKm} km</Text>\n    </View>\n  );\n});\n\nexport default function HikeLog() {\n  const hikes = useHikes();\n  const [now, setNow] = useState(Date.now());\n\n  useEffect(() => {\n    const id = setInterval(() => setNow(Date.now()), 16);\n    return () => clearInterval(id);\n  }, []);\n\n  return (\n    <View style={{ flex: 1 }}>\n      <Text>Updated {new Date(now).toLocaleTimeString()}</Text>\n      <FlatList\n        data={hikes}\n        keyExtractor={(item, index) => String(index)}\n        renderItem={({ item }) => <HikeRow hike={{ ...item }} />}\n      />\n    </View>\n  );\n}","bugLines":[19,28,29],"title":"لماذا يتقطّع سجلّ الرحلات؟","prompt":"<p>يتقطّع سجلّ Trailhead على هاتف Android متوسط الفئة، حتى في وضع الإنتاج. يُظهر Performance Monitor أن <strong>الـ JS thread</strong> يُسقط إطارات بينما الـ UI thread بخير، ويُظهر الـ Profiler أن كل <code dir=\"ltr\">HikeRow</code> يُعاد رسمه باستمرار. اختر <strong>الأسطر الثلاثة</strong> المسؤولة.</p>\n","hints":["شيء ما يجعل الشاشة كلها يُعاد رسمها مرات كثيرة في الثانية. كم مرة يُطلَق مؤقّت بفاصل 16 ms؟","لا يتخطّى <code dir=\"ltr\">memo</code> إعادة الـ render إلا حين يكون كل prop هو الكائن نفسه الذي كان في المرة السابقة.","انظر أيضًا إلى طريقة تعريف الصفوف: ماذا يحدث للمفاتيح حين تُدرج رحلة في الأعلى؟"],"explanation":"<p>السطر 19 يحدّث الـ state كل 16 ms، فيعيد رسم الشاشة كلها نحو 60 مرة في الثانية فقط لعرض ساعة بدقّة ثانية واحدة؛ استخدم فاصلًا أطول بكثير، وانقل الساعة إلى component صغير خاص بها حتى لا يُعاد رسم غيرها. والسطر 29 يمرّر <code dir=\"ltr\">{ ...item }</code>، وهو كائن جديد تمامًا في كل render، فلا يتمكّن <code dir=\"ltr\">memo(HikeRow)</code> أبدًا من تخطّي صفّ؛ مرّر <code dir=\"ltr\">hike={item}</code>. والسطر 28 يجعل مفتاح الصفوف هو الفهرس، فإدراج رحلة في الأعلى يُزيح كل المفاتيح، ويُعاد تركيب كل صف؛ استخدم <code dir=\"ltr\">keyExtractor={(hike) =&gt; hike.id}</code>. أما كائنات <code dir=\"ltr\">style</code> المباشرة داخل <code dir=\"ltr\">HikeRow</code> فليست المشكلة: فهي لا تُنشأ إلا حين يُرسم الصف فعلًا.</p>\n","checkLabels":{}},"words":782,"readMinutes":5}