{"id":"l-rn-2-1","courseId":"react-native-apps","lang":"ar","kind":"lesson","summary":"نسّق Trailhead باستخدام StyleSheet ومصفوفات التنسيق والـ design tokens، مستخدمًا نقاطًا بلا وحدات وخصائص مفصّلة، في نظام بلا cascade ولا selectors ولا media queries.","html":"<p>في أول مرة تنسّق فيها شاشة في React Native، ستمدّ يدك إلى أشياء غير موجودة: اسم class، و<code dir=\"ltr\">:hover</code>، وmedia query، و<code dir=\"ltr\">font-family</code> على <code dir=\"ltr\">body</code> ليرثه كل شيء. لا يملك React Native أيًّا منها. ما يملكه بدلًا من ذلك أصغر وأكثر قابلية للتوقّع: كائنات عادية، تُطبَّق مباشرة على الـ component الذي يحتاج إليها، بترتيب تتحكّم فيه أنت.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">كائنات التنسيق وStyleSheet.create</h2>\n<p>التنسيق (style) كائن JavaScript بمفاتيح camelCase. يمكنك تمريره مباشرة، لكن معظم الكود يجمع التنسيقات باستخدام <code dir=\"ltr\">StyleSheet.create</code>، الذي يمنحك الإكمال التلقائي وفحص الأنواع ومكانًا واحدًا تقرأ فيه تنسيقات الـ component:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">src/components/HikeCard.tsx</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:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { StyleSheet, Text, View } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react-native'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> HikeCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">distanceKm</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">distanceKm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">styles.card</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Text</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">styles.title</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Text</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">styles.meta</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">distanceKm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> km&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Text</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> styles</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> StyleSheet.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">create</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  card: {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    backgroundColor: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#ffffff'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    borderRadius: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    paddingVertical: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    paddingHorizontal: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    borderWidth: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    borderColor: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#e2e8e4'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  title: { fontSize: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">18</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, fontWeight: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'600'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, color: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#1d2a22'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  meta: { fontSize: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">14</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, color: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#5c6b62'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, marginTop: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">});</span></span></code></pre></div>\n<p>تعريف <code dir=\"ltr\">styles</code> أسفل الـ component عُرف متّبع، وهو عُرف جيد: الـ JSX يأتي أولًا حين تفتح الملف.</p>\n<p>هل <code dir=\"ltr\">StyleSheet.create</code> أسرع من الكائنات المباشرة (inline)؟ ليس بأي قدر ستقيسه اليوم. الكلفة الحقيقية للكائنات المباشرة هي سهولة القراءة، وإنشاء كائنات جديدة في كل render، وهذا لا يهمّ إلا حين تمرّر التنسيقات إلى أبناء مغلّفين بـ memo. القاعدة المعقولة: التنسيقات الثابتة في <code dir=\"ltr\">StyleSheet.create</code>، والقيم القليلة التي تعتمد على الـ props أو الـ state تُكتب مباشرة أو في مصفوفة التنسيق بجوارها.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">أرقام بلا وحدات</h2>\n<p><code dir=\"ltr\">borderRadius: 12</code> تعني 12 <strong>نقطة مستقلّة عن الكثافة</strong> (density-independent points)، لا 12 بكسلًا فعليًا. الزاوية ذات الـ 12 نقطة تبدو بالحجم نفسه على هاتف Android رخيص وعلى iPhone بكثافة بكسلات أعلى بثلاث مرات؛ فالمنصّة تتولّى الضرب نيابةً عنك. النسب المئوية تعمل كنصوص (<code dir=\"ltr\">width: '50%'</code>)، وهذا كل شيء: لا <code dir=\"ltr\">px</code> ولا <code dir=\"ltr\">rem</code> ولا <code dir=\"ltr\">em</code> ولا <code dir=\"ltr\">vh</code>.</p>\n<p>والاختصارات التي تأخذ عدّة قيم غير موجودة أيضًا. بدلًا من <code dir=\"ltr\">margin: '8px 16px'</code> تكتب <code dir=\"ltr\">marginVertical: 8, marginHorizontal: 16</code>. والنمط نفسه يشمل الحشو (<code dir=\"ltr\">paddingTop</code>، <code dir=\"ltr\">paddingHorizontal</code>)، والحدود (<code dir=\"ltr\">borderTopWidth</code>، <code dir=\"ltr\">borderBottomColor</code>)، والزوايا (<code dir=\"ltr\">borderTopLeftRadius</code>). أما الاختصارات ذات القيمة الواحدة مثل <code dir=\"ltr\">margin: 8</code> و<code dir=\"ltr\">borderRadius: 12</code> فتعمل كما تتوقّع.</p>\n<p>يدعم React Native الحديث أيضًا عدّة خصائص يفتقدها مطوّرو الويب: <code dir=\"ltr\">gap</code> و<code dir=\"ltr\">rowGap</code> و<code dir=\"ltr\">columnGap</code> للمسافات بين الأبناء، و<code dir=\"ltr\">boxShadow</code> بنصّ يشبه CSS، و<code dir=\"ltr\">filter</code>. أما الدروس القديمة فتستخدم props منفصلة هي <code dir=\"ltr\">shadowColor</code>/<code dir=\"ltr\">shadowOffset</code> لـ iOS و<code dir=\"ltr\">elevation</code> لـ Android، وما زلت ستراها في قواعد الكود الموجودة.</p>\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>كتابة نصوص CSS</span></p>\n<p><code dir=\"ltr\">fontSize: '16px'</code> أو <code dir=\"ltr\">margin: '8 16'</code> إما ترمي خطأ بشاشة حمراء أو تُتجاهَل بصمت، بحسب الخاصية. إن بدا أن تنسيقًا لا يفعل شيئًا، فابحث أولًا عن وحدة أو نصّ متعدّد القيم. وTypeScript تلتقط معظم هذه الأخطاء لحظة كتابتها، وهذا سبب إضافي لإبقائها مفعّلة.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">مصفوفات التنسيق: الترتيب هو القاعدة الوحيدة</h2>\n<p>يقبل الـ prop المسمّى <code dir=\"ltr\">style</code> مصفوفة. يدمجها React Native من اليسار إلى اليمين، فتفوز المفاتيح اللاحقة، وتُتخطّى القيم الزائفة (<code dir=\"ltr\">false</code> و<code dir=\"ltr\">null</code> و<code dir=\"ltr\">undefined</code>). وهذا يحوّل التنسيق الشرطي إلى سطر واحد:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TSX</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:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[styles.card, isFavorite </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x26;&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> styles.favorite, isSelected </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x26;&#x26;</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> styles.selected]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span></code></pre></div>\n<p>لا توجد specificity تفكّر فيها، ولا <code dir=\"ltr\">!important</code>، ولا تنسيق يصل من أب على بُعد ثلاثة ملفات. إن بدت بطاقة خاطئة، فالسبب في تلك المصفوفة.</p>\n<p>استخدم القاعدة نفسها لتجعل الـ components الخاصة بك قابلة للتعديل. اقبل prop باسم <code dir=\"ltr\">style</code> وضعه في الآخر:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TSX</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:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { StyleProp, ViewStyle } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'react-native'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Props</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">distanceKm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> StyleProp</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ViewStyle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> HikeCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">distanceKm</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">style</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Props</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[styles.card, style]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      {</span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* …title and meta as before */</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">View</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  );</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// A caller nudges one instance without forking the component:</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">HikeCard</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Ridge Loop\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> distanceKm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">8.4</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> style</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{ marginBottom: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">24</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> /></span></span></code></pre></div>\n<p><code dir=\"ltr\">StyleProp&lt;ViewStyle&gt;</code> هو الـ type الذي يقبل كائنًا أو مصفوفة أو قيمة زائفة، تمامًا مثل الـ components المدمجة.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ tokens بدلًا من الـ cascade</h2>\n<p>دون وراثة، يأتي الاتساق من مكانين: الـ components (بطاقة <code dir=\"ltr\">HikeCard</code> واحدة، وزر <code dir=\"ltr\">PrimaryButton</code> واحد، و<code dir=\"ltr\">AppText</code> واحد)، وملف صغير من الـ <strong>tokens</strong> (قيم التصميم المشتركة) تستورده كلها.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">src/theme.ts</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:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> colors</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  bg: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#f6f8f6'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  surface: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#ffffff'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  text: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#1d2a22'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  textMuted: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#5c6b62'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  primary: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#2f6f4e'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  border: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#e2e8e4'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> space</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { xs: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, sm: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, md: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, lg: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, xl: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">24</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">export</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> radius</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { sm: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">8</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, md: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">12</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, lg: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">20</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span></code></pre></div>\n<p>الآن تقرأ البطاقة <code dir=\"ltr\">borderRadius: radius.md</code> و<code dir=\"ltr\">paddingHorizontal: space.lg</code>، وأي تغيير من المصمّم يقع في ملف واحد.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الوضع الداكن بالـ tokens نفسها</span></p>\n<p>تُرجع <code dir=\"ltr\">useColorScheme()</code> من <code dir=\"ltr\">react-native</code> القيمة <code dir=\"ltr\">'dark'</code> حين يكون الهاتف في الوضع الداكن. احتفظ بكائنَي tokens، واختر أحدهما في أعلى الشجرة، ومرّره إلى الأسفل عبر الـ context؛ ولا يكتب أي component لونًا ثابتًا بنفسه. ستبني هذا الـ context في القسم 3.</p>\n</aside>\n<h2 id=\"sec-5\" tabindex=\"-1\">لماذا غياب الـ cascade ميزة</h2>\n<p>في تطبيق ويب كبير، تأتي أصعب أخطاء التنسيق من قواعد لم تكتبها تصل إلى عناصر لم تتوقّعها. يُلغي React Native هذه الفئة كلها. شكل الـ component هو تنسيقه الخاص زائد ما يمرّره له من يستخدمه، ولا شيء غير ذلك. هذا يعني كتابة أكثر في البداية، وتصحيح أخطاء أقل بكثير لاحقًا. والفرق التي تريد سهولة تشبه الـ classes تضيف مكتبة مثل NativeWind (classes من Tailwind تُترجَم إلى كائنات تنسيق)، لكنها في الأسفل تبقى الكائنات نفسها.</p>\n<p>في الدرس التالي سترتّب هذه الصناديق المنسّقة باستخدام flexbox، حيث تختلف القيم الافتراضية في React Native عن الويب بطرق تفسّر معظم لحظات «لماذا هذا العنصر منضغط؟».</p>\n","toc":[{"id":"sec-1","text":"كائنات التنسيق وStyleSheet.create","level":2},{"id":"sec-2","text":"أرقام بلا وحدات","level":2},{"id":"sec-3","text":"مصفوفات التنسيق: الترتيب هو القاعدة الوحيدة","level":2},{"id":"sec-4","text":"الـ tokens بدلًا من الـ cascade","level":2},{"id":"sec-5","text":"لماذا غياب الـ cascade ميزة","level":2}],"takeaways":["التنسيقات كائنات JavaScript بمفاتيح camelCase وأرقام بلا وحدات، تُقاس بنقاط مستقلّة عن كثافة البكسلات.","اختصارات CSS متعدّدة القيم، مثل <code dir=\"ltr\">margin '8px 16px'</code>، غير موجودة؛ استخدم <code dir=\"ltr\">marginVertical</code> و<code dir=\"ltr\">marginHorizontal</code>.","في مصفوفة التنسيق تفوز العناصر اللاحقة وتُتخطّى القيم الزائفة (falsy)، وهذا يجعل التنسيق الشرطي سطرًا واحدًا.","إعادة الاستخدام تتم عبر الـ components وكائن من الـ tokens، لا عبر الـ selectors أو ملف تنسيق عام.","اقبل prop باسم <code dir=\"ltr\">style</code> وضعه في آخر المصفوفة، ليتمكّن من يستخدم الـ component من تعديله دون نسخه."],"further":[{"title":"Style","url":"https://reactnative.dev/docs/style"},{"title":"StyleSheet","url":"https://reactnative.dev/docs/stylesheet"},{"title":"useColorScheme","url":"https://reactnative.dev/docs/usecolorscheme"}],"quiz":[{"q":"<p>ما الذي يُرسَم مع <code dir=\"ltr\">style={[styles.card, isFavorite &amp;&amp; styles.favorite, { padding: 8 }]}</code> حين تكون <code dir=\"ltr\">isFavorite</code> تساوي <code dir=\"ltr\">false</code> وكلا التنسيقين يضبط <code dir=\"ltr\">padding</code>؟</p>","options":[{"text":"يرمي التطبيق خطأ، لأن <code dir=\"ltr\">false</code> ليست تنسيقًا صالحًا.","why":"القيم الزائفة في مصفوفة التنسيق تُتخطّى عمدًا؛ وهذا ما يجعل نمط <code dir=\"ltr\">&amp;&amp;</code> آمنًا هنا."},{"text":"الحشو الخاص بـ <code dir=\"ltr\">styles.card</code>، لأن العنصر الأول له الأولوية.","why":"الأولوية في الاتجاه المعاكس. العناصر اللاحقة تتغلّب على السابقة، مثل <code dir=\"ltr\">Object.assign</code>."},{"text":"الحشو الخاص بـ <code dir=\"ltr\">styles.favorite</code>، لأن التنسيقات المسمّاة تتغلّب على الكائنات المباشرة.","why":"لا توجد أولوية تحديد (specificity) في React Native. الترتيب هو القاعدة الوحيدة، و<code dir=\"ltr\">favorite</code> تُخطّي على أي حال."},{"text":"حشو قيمته 8، لأن العنصر الأخير في المصفوفة هو الذي يفوز.","why":"صحيح. تُتجاهَل <code dir=\"ltr\">false</code>، ويأتي الكائن المباشر أخيرًا، فيتغلّب الـ <code dir=\"ltr\">padding</code> الخاص به على حشو البطاقة."}],"answer":3},{"q":"<p>أيّ كائن تنسيق صالح في React Native؟</p>","options":[{"text":"<code dir=\"ltr\">{ margin: '8px 16px', fontSize: '16px' }</code>","why":"الاختصارات متعدّدة القيم ونصوص px من صياغة CSS. أما React Native فيريد رقمًا واحدًا لكل خاصية."},{"text":"<code dir=\"ltr\">{ marginVertical: 8, marginHorizontal: 16, fontSize: 16 }</code>","why":"صحيح. مفاتيح مفصّلة بأرقام بلا وحدات، تُقاس بالنقاط."},{"text":"<code dir=\"ltr\">{ 'margin-vertical': 8, 'font-size': 16 }</code>","why":"المفاتيح أسماء خصائص JavaScript بصيغة camelCase، لا أسماء CSS بصيغة kebab-case."}],"answer":1},{"q":"<p>يحتاج Trailhead أن تستخدم كل البطاقات نصف قطر الزوايا والمسافات نفسها. ما الطريقة المعتادة؟</p>","options":[{"text":"ملف <code dir=\"ltr\">theme.ts</code> يصدّر tokens (الألوان، المسافات، نصف القطر) تستوردها الـ components داخل الـ StyleSheets الخاصة بها.","why":"صحيح. الـ tokens تعطيك مصدرًا واحدًا للحقيقة دون حاجة إلى cascade، ومن السهل تبديلها للوضع الداكن."},{"text":"ملف تنسيق عام يُحمَّل في الـ root layout ويستهدف كل <code dir=\"ltr\">View</code>.","why":"لا توجد selectors في React Native، فلا شيء يستطيع استهداف «كل View»."},{"text":"نسخ الأرقام في كل component والحفاظ على تطابقها بالبحث والاستبدال.","why":"ينجح هذا حتى يغيّر المصمّم نصف القطر من 12 إلى 14 في أربعين ملفًا."}],"answer":0}],"exercise":{"kind":"web","mode":"js","starter":{"js":"// Merge a React Native style value into one plain object.\n// A style can be an object, a falsy value, or an array of styles (arrays can nest).\nfunction flattenStyle(style) {\n  if (Array.isArray(style)) {\n    return Object.assign(style[0], ...style.slice(1));\n  }\n  return style;\n}\n\nconst card = { padding: 12, borderRadius: 12 };\nconsole.log(flattenStyle([card, false, { padding: 8 }]));\n"},"solution":{"js":"function flattenStyle(style) {\n  if (!style) return {};\n  if (!Array.isArray(style)) return { ...style };\n  const result = {};\n  for (const entry of style) {\n    Object.assign(result, flattenStyle(entry));\n  }\n  return result;\n}\n\nconst card = { padding: 12, borderRadius: 12 };\nconsole.log(flattenStyle([card, false, { padding: 8 }]));\n"},"checks":[{"id":"single-object","test":"const eq = (a, b) => a !== null && typeof a === 'object' && JSON.stringify(Object.entries(a).sort()) === JSON.stringify(Object.entries(b).sort()); return typeof flattenStyle === 'function' && eq(flattenStyle({ padding: 4 }), { padding: 4 });"},{"id":"later-wins","test":"const eq = (a, b) => a !== null && typeof a === 'object' && JSON.stringify(Object.entries(a).sort()) === JSON.stringify(Object.entries(b).sort()); return eq(flattenStyle([{ padding: 12, color: 'green' }, { padding: 8 }]), { padding: 8, color: 'green' });"},{"id":"skips-falsy","test":"const eq = (a, b) => a !== null && typeof a === 'object' && JSON.stringify(Object.entries(a).sort()) === JSON.stringify(Object.entries(b).sort()); return eq(flattenStyle([{ a: 1 }, false, null, undefined, { b: 2 }]), { a: 1, b: 2 });"},{"id":"nested-arrays","test":"const eq = (a, b) => a !== null && typeof a === 'object' && JSON.stringify(Object.entries(a).sort()) === JSON.stringify(Object.entries(b).sort()); return eq(flattenStyle([{ a: 1 }, [{ b: 2 }, [false, { a: 3 }]]]), { a: 3, b: 2 });"},{"id":"no-mutation","test":"const base = { padding: 12 }; const extra = { padding: 8 }; flattenStyle([base, extra]); flattenStyle(base).padding = 99; return base.padding === 12 && extra.padding === 8;"},{"id":"falsy-input","test":"const eq = (a, b) => a !== null && typeof a === 'object' && JSON.stringify(Object.entries(a).sort()) === JSON.stringify(Object.entries(b).sort()); return eq(flattenStyle(null), {}) && eq(flattenStyle(false), {}) && eq(flattenStyle([]), {});"}],"title":"اكتب دالة دمج التنسيقات بنفسك","prompt":"<p>يدمج React Native الـ prop المسمّى <code dir=\"ltr\">style</code> بالطريقة نفسها في كل مرة: تُقرأ المصفوفات من اليسار إلى اليمين، وتفوز المفاتيح اللاحقة، وتُتخطّى القيم الزائفة، ويمكن أن تتداخل المصفوفات. اكتب <code dir=\"ltr\">flattenStyle(style)</code> بحيث تتّبع هذه القواعد وتُرجع <strong>كائنًا عاديًا جديدًا واحدًا</strong>.</p>\n<ul>\n<li>الكائن يُرجع نسخة منه.</li>\n<li>القيمة الزائفة (<code dir=\"ltr\">false</code> أو <code dir=\"ltr\">null</code> أو <code dir=\"ltr\">undefined</code>) أو المصفوفة الفارغة تُرجع <code dir=\"ltr\">{}</code>.</li>\n<li>يجب ألا تعدّل أبدًا كائنات التنسيق التي تمرّرها إليها.</li>\n</ul>\n<p>الكود المبدئي قريب من الحل لكن فيه ثلاثة أخطاء. اعثر عليها.</p>\n","hints":["<code dir=\"ltr\">Object.assign(style[0], …)</code> يكتب داخل الكائن الأول. ماذا يحدث لـ <code dir=\"ltr\">styles.card</code> المشترك في المرة التالية التي يُستخدم فيها؟","تعامل مع الحالات البسيطة أولًا: المدخل الزائف يُرجع <code dir=\"ltr\">{}</code>، والكائن الذي ليس مصفوفة يُرجع <code dir=\"ltr\">{ ...style }</code>.","للمصفوفات، ابدأ بـ <code dir=\"ltr\">const result = {}</code> ثم <code dir=\"ltr\">Object.assign(result, flattenStyle(entry))</code> لكل عنصر؛ والاستدعاء التعاودي يتكفّل بالتداخل وبالقيم الزائفة."],"explanation":"<p>الحل تعاودي (recursive): كل عنصر يُسطَّح وحده، وهذا يعالج المصفوفات المتداخلة والقيم الزائفة بحالتَي الأساس نفسيهما، ثم يُدمَج في كائن جديد فلا تُمَسّ تنسيقات المستدعي أبدًا. تعديل <code dir=\"ltr\">style[0]</code> هو الخطأ الخطير: <code dir=\"ltr\">styles.card</code> مشترك بين كل البطاقات، فبطاقة مفضّلة واحدة ستغيّرها كلها بصمت. هذا هو العقد نفسه الذي يتبعه <code dir=\"ltr\">StyleSheet.flatten</code>، ومعرفته تتيح لك التنبّؤ بالضبط بما ستعرضه أي مصفوفة تنسيق.</p>\n","checkLabels":{"single-object":"الكائن المفرد يعود بالمفاتيح نفسها","later-wins":"العناصر اللاحقة تتغلّب على السابقة","skips-falsy":"تُتخطّى عناصر false وnull وundefined","nested-arrays":"تُسطَّح المصفوفات المتداخلة بالترتيب","no-mutation":"لا تُعدَّل كائنات التنسيق المُدخلة أبدًا","falsy-input":"المدخل الزائف والمصفوفات الفارغة تُرجع {}"}},"words":666,"readMinutes":4}