{"id":"l-rn-3-2","courseId":"react-native-apps","lang":"ar","kind":"lesson","summary":"أضف إلى Trailhead أشرطة tabs سفلية باستخدام مجموعة مسارات، وشاشة لتفاصيل الرحلة عبر مسار ديناميكي [id]، واقرأ الـ params الخاصة بها بأمان، متذكّرًا أن الـ params تصل دائمًا نصوصًا.","html":"<p>صار في Trailhead الآن أكثر من مكان يمكن أن تكون فيه: سجلّ الرحلات، وصفحة الإحصاءات، والإعدادات، إضافةً إلى شاشة تفاصيل لكل رحلة. الثلاثة الأولى نظائر يتنقّل المستخدمون بينها طوال الوقت؛ أما شاشة التفاصيل فشيء تتعمّق فيه ثم تعود منه. هذا هو الشكل الكلاسيكي لتطبيقات الجوّال: <strong>tabs</strong> للنظائر، و<strong>stack</strong> للتعمّق.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">مجموعة مسارات للـ tabs</h2>\n<p>هذه بنية الملفات التي يستخدمها Trailhead:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Text</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>src/app/\n  _layout.tsx          root Stack\n  (tabs)/\n    _layout.tsx        Tabs navigator\n    index.tsx          /          (Log)\n    stats.tsx          /stats\n    settings.tsx       /settings\n  hikes/\n    [id].tsx           /hikes/42  (pushed over the tabs)</code></pre></div>\n<p>المجلّد بين قوسين <strong>مجموعة مسارات</strong> (route group). تتيح لشاشات الـ tabs الثلاث أن تتشارك layout دون أن تضيف شيئًا إلى عناوينها: <code dir=\"ltr\">(tabs)/stats.tsx</code> هو <code dir=\"ltr\">/stats</code>، لا <code dir=\"ltr\">/tabs/stats</code>.</p>\n<p>يُرجع layout الـ tabs أداة التنقّل <code dir=\"ltr\">Tabs</code>، مع <code dir=\"ltr\">Tabs.Screen</code> واحد لكل tab، بالترتيب الذي يجب أن تظهر به:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">src/app/(tabs)/_layout.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\"> Ionicons </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '@expo/vector-icons/Ionicons'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Tabs } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'expo-router'</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\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> TabLayout</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </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\">Tabs</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> screenOptions</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{ tabBarActiveTintColor: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'#2f6f4e'</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Tabs.Screen</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"index\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        options</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\">          title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Log'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">          tabBarIcon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: ({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">size</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) </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\">Ionicons</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\">\"list\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> color</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">color</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">size</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\">        }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Tabs.Screen</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"stats\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        options</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\">          title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Stats'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">          tabBarIcon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: ({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">size</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) </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\">Ionicons</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\">\"stats-chart\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> color</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">color</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">size</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\">        }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Tabs.Screen</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"settings\"</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">        options</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\">          title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Settings'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">          tabBarIcon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: ({ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">color</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">size</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) </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\">Ionicons</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\">\"settings-outline\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> color</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">color</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> size</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">size</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\">        }</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">}</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\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Tabs</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></code></pre></div>\n<p>ثم يضع الـ root layout مجموعة الـ tabs كلها داخل الـ Stack، مع إخفاء ترويسة المجموعة نفسها (فلكل tab ترويسته)، ويليها مسار التفاصيل:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">src/app/_layout.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\"> { Stack } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'expo-router'</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\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> RootLayout</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </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\">Stack</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\">Stack.Screen</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\">\"(tabs)\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> options</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{ headerShown: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Stack.Screen</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\">\"hikes/[id]\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> options</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{ title: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Hike'</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    &#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Stack</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></code></pre></div>\n<p>ولأن <code dir=\"ltr\">hikes/[id]</code> ينتمي إلى الـ Stack الجذري لا إلى الـ tabs، فإن فتح رحلة يُدخل شاشة كاملة منزلقة فوق شريط الـ tabs، وتعيدك إيماءة الرجوع إلى الـ tab الذي جئت منه. هذا ما تفعله معظم تطبيقات iOS وAndroid، ويعطي شاشة التفاصيل ارتفاع الهاتف كله.</p>\n<p>عادتان تُبقيان الـ tabs مريحة. اجعلها بين ثلاث وخمس وجهات رئيسية يزورها المستخدمون كثيرًا؛ فالـ tab السادس يعني عادةً أن بعضها مكانه داخل الإعدادات. وتذكّر أن الـ tabs تبقى مركّبة بعد زيارتها: انتقل من Log إلى Stats ثم عُد، وسيحتفظ السجلّ بموضع تمريره. هذا هو السلوك الذي يتوقّعه المستخدمون، فلا تُعِد ضبطه. وإن احتاج tab واحد إلى شاشات تعمّق خاصة به تُبقي شريط الـ tabs ظاهرًا، فأعطِ ذلك الـ tab مجلّدًا بملف <code dir=\"ltr\">_layout.tsx</code> خاص به يُرجع <code dir=\"ltr\">Stack</code>؛ فتداخل أدوات التنقّل ليس إلا تداخلًا للمجلّدات.</p>\n<aside class=\"callout callout-note\" 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\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4\"/><path d=\"M12 8h.01\"/></svg><span><span class=\"callout-kind\">ملاحظة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الـ tabs الأصلية</span></p>\n<p>يرسم الـ component المسمّى <code dir=\"ltr\">Tabs</code> شريطه باستخدام JavaScript، فيمكنك تنسيقه بحرّية. ويوفّر Expo Router أيضًا tabs أصلية (native tabs) تستخدم شريط الـ tabs الخاص بالنظام، بما في ذلك أحدث أنماطه البصرية، مع مجال أضيق للتنسيق المخصّص. تغيّر مسار الاستيراد بين إصدارات الـ SDK، فاتّبع وثائق Expo Router الخاصة بالـ SDK لديك إن اخترتها.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">المسارات الديناميكية والـ params</h2>\n<p>الملف المسمّى بأقواس مربّعة، <code dir=\"ltr\">[id].tsx</code>، يطابق أي جزء مفرد من الـ URL ويكشفه بوصفه param اسمه <code dir=\"ltr\">id</code>. اقرأه باستخدام <code dir=\"ltr\">useLocalSearchParams</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">src/app/hikes/[id].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\"> { Stack, useLocalSearchParams } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'expo-router'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { 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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { useHike } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '../../state/hikes'</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\"> default</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> HikeDetail</span><span style=\"color:#953800;--shiki-dark:#FFA657\">() </span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useLocalSearchParams</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;{ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> hike</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useHike</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(id);</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">hike) {</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\">{ flex: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, padding: </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>\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:#1F2328;--shiki-dark:#E6EDF3\">>That hike doesn't exist anymore.&#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\">  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\">{ flex: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, padding: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16</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>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Stack.Screen</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> options</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{ title: hike.name }</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\">{ fontSize: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">16</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:#CF222E;--shiki-dark:#FF7B72\">{</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">hike.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></code></pre></div>\n<p>وللوصول إليها، اربط إلى الـ URL. إما أن تبني النص بنفسك، أو تدع Expo Router يملأ النمط:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"4\"><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\">Link</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> href</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`/hikes/${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">hike</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">id</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</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\">hike.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\">Link</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">Link</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> href</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">={</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">{ pathname: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'/hikes/[id]'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, params: { id: hike.id } }</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\">hike.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\">Link</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>أي شيء في <code dir=\"ltr\">params</code> ليس جزءًا من المسار يصبح query string: فـ <code dir=\"ltr\">params: { id: hike.id, units: 'mi' }</code> ينتج <code dir=\"ltr\">/hikes/42?units=mi</code>، وتقرأ شاشة التفاصيل <code dir=\"ltr\">units</code> بالـ hook نفسه.</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>الثقة بنوع الـ param</span></p>\n<p>يبدو <code dir=\"ltr\">useLocalSearchParams&lt;{ id: string }&gt;()</code> مضبوط النوع، لكن الـ generic وعد تقطعه لـ TypeScript، لا عملية تحويل. الـ params تأتي من URL، فهي <strong>نصوص</strong>، والمفتاح المكرّر في الـ query (<code dir=\"ltr\">?tag=lake&amp;tag=steep</code>) يصل <strong>مصفوفةً</strong> من النصوص. ويمكن أيضًا أن يحتوي deep link من بريد قديم على أي شيء: <code dir=\"ltr\">/hikes/abc</code>، <code dir=\"ltr\">/hikes/-1</code>. حوّل وتحقّق وتعامل مع حالة «غير موجود» في كل شاشة لها params.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">المعرّفات في الـ params، والبيانات من الـ state</h2>\n<p>مرّر أصغر شيء يعرّف البيانات، وهو معرّف في العادة، وابحث عن البيانات في الشاشة الوجهة. يبدو تمرير كائن رحلة كامل مريحًا، لكن الـ params تُحوَّل إلى نصّ داخل الـ URL: يصبح الكائن نصًّا، ولا يستطيع deep link إعادة إنشائه أبدًا، وإن عدّل المستخدم الرحلة، تعرض شاشة التفاصيل النسخة القديمة التي سُلّمت لها. الدرس التالي يبني الـ state المشتركة التي يقرأ منها <code dir=\"ltr\">useHike(id)</code>.</p>\n<p>يُرجع <code dir=\"ltr\">useLocalSearchParams</code> الـ params الخاصة بالشاشة التي يُستدعى فيها، وهذا ما تريده في الغالبية العظمى من الحالات. أما شقيقه <code dir=\"ltr\">useGlobalSearchParams</code> فيتحدّث كلما تغيّر أي مسار، حتى المسارات في الخلفية، وهذا يسبّب عمليات render إضافية؛ احتفظ به لأشياء مثل التحليلات (analytics) التي تهتم فعلًا بكل عملية تنقّل.</p>\n<p>التالي: الـ state التي تقف وراء عمليات البحث هذه، أي مصدر واحد للحقيقة عن الرحلات، تتشاركه كل الـ tabs والشاشات.</p>\n","toc":[{"id":"sec-1","text":"مجموعة مسارات للـ tabs","level":2},{"id":"sec-2","text":"المسارات الديناميكية والـ params","level":2},{"id":"sec-3","text":"المعرّفات في الـ params، والبيانات من الـ state","level":2}],"takeaways":["المجلّد بين قوسين، مثل <code dir=\"ltr\">(tabs)</code>، يجمّع المسارات تحت layout دون أن يضيف جزءًا إلى الـ URL.","ضع أداة تنقّل الـ tabs في <code dir=\"ltr\">(tabs)/_layout.tsx</code>، وأضف شاشات التفاصيل من الـ Stack الجذري لتغطّي شريط الـ tabs.","الملف المسمّى <code dir=\"ltr\">[id].tsx</code> يطابق أي جزء مفرد من الـ URL؛ اقرأه باستخدام <code dir=\"ltr\">useLocalSearchParams()</code>.","الـ params في المسارات نصوص (أو مصفوفات من النصوص)، فحوّلها وتحقّق منها قبل الاستخدام.","مرّر المعرّفات في الـ params، لا الكائنات كاملة أبدًا، وابحث عن البيانات في الشاشة الوجهة."],"further":[{"title":"Navigation layouts","url":"https://docs.expo.dev/router/basics/navigation-layouts/"},{"title":"Tabs","url":"https://docs.expo.dev/router/advanced/tabs/"},{"title":"URL parameters","url":"https://docs.expo.dev/router/reference/url-parameters/"}],"quiz":[{"q":"<p>ما الـ URL الذي يخدمه <code dir=\"ltr\">src/app/(tabs)/stats.tsx</code>؟</p>","options":[{"text":"<code dir=\"ltr\">/(tabs)/stats</code>","why":"الأقواس تحدّد مجموعة، والمجموعات لا تظهر في الـ URL أبدًا."},{"text":"<code dir=\"ltr\">/tabs/stats</code>","why":"يُحذف اسم المجموعة كله، لا الأقواس وحدها."},{"text":"<code dir=\"ltr\">/stats</code>","why":"صحيح. المجموعة <code dir=\"ltr\">(tabs)</code> تحدّد فقط أي layout يغلّف الشاشة."}],"answer":2},{"q":"<p>شاشة التفاصيل تفعل هذا، والرحلة ذات المعرّف 7 لا تُطابَق أبدًا. لماذا؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"tsx\" data-index=\"0\"><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\">const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> useLocalSearchParams</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;{ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> hike</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hikes.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">find</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">h</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> id); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// h.id is a number</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">useLocalSearchParams</code> يعمل فقط داخل شاشات الـ tabs.","why":"يعمل في أي مسار. المشكلة في المقارنة، لا في مكان تشغيل الـ hook."},{"text":"<code dir=\"ltr\">id</code> هو النص “7”، و<code dir=\"ltr\">7 === &quot;7&quot;</code> قيمتها false؛ حوّله باستخدام <code dir=\"ltr\">Number(id)</code> وتحقّق من النتيجة.","why":"صحيح. الـ params في الـ URL نصوص دائمًا. الـ generic يحدّد نوعها فقط، ولا يحوّل أي شيء."},{"text":"يجب أن يكون اسم ملف المسار <code dir=\"ltr\">[hikeId].tsx</code> ليصل الـ param.","why":"اسم الملف يحدّد اسم الـ param فقط. و<code dir=\"ltr\">[id].tsx</code> يعطيك <code dir=\"ltr\">id</code>."}],"answer":1},{"q":"<p>يمرّر زميلك كائن رحلة كاملًا بوصفه param في المسار حتى لا تحتاج شاشة التفاصيل إلى البحث عنه. ما المشكلة الرئيسية؟</p>","options":[{"text":"الـ params تُحوَّل إلى نصّ داخل الـ URL، فيصبح الكائن نصًّا، ولا تستطيع الـ deep links إعادة إنشائه، وتعرض الشاشة بيانات قديمة بعد التعديل.","why":"صحيح. مرّر المعرّف واقرأ الرحلة الحالية من الـ state أو التخزين في الشاشة الوجهة."},{"text":"الـ params في المسارات محدودة بحرف واحد.","why":"لا يوجد حدّ كهذا. المشكلة أن الـ URLs تحمل نصوصًا، لا كائنات."},{"text":"الكائنات في الـ params تجعل التطبيق ينهار على Android فقط.","why":"يسيء التصرّف بالطريقة نفسها على المنصّتين. لكنه لا ينهار."},{"text":"يشفّر Expo Router الـ params، فسيكون الأمر بطيئًا.","why":"الـ params ليست مشفّرة؛ إنها جزء من URL. السرعة ليست المشكلة."}],"answer":0}],"exercise":{"kind":"web","mode":"js","starter":{"js":"// params is whatever useLocalSearchParams() returned for /hikes/[id]\n// Return { id, units, photo } with real types and safe defaults.\nfunction parseHikeParams(params) {\n  return { id: params.id, units: params.units, photo: params.photo };\n}\n\nconsole.log(parseHikeParams({ id: '42', units: 'MI', photo: '2' }));\nconsole.log(parseHikeParams({ id: 'abc' }));\n"},"solution":{"js":"function first(value) {\n  return Array.isArray(value) ? value[0] : value;\n}\n\nfunction parseHikeParams(params) {\n  const rawId = first(params.id);\n  const id = /^\\d+$/.test(rawId ?? '') && Number(rawId) > 0 ? Number(rawId) : null;\n\n  const rawUnits = String(first(params.units) ?? '').toLowerCase();\n  const units = rawUnits === 'mi' ? 'mi' : 'km';\n\n  const rawPhoto = first(params.photo);\n  const photo = /^\\d+$/.test(rawPhoto ?? '') ? Number(rawPhoto) : 0;\n\n  return { id, units, photo };\n}\n\nconsole.log(parseHikeParams({ id: '42', units: 'MI', photo: '2' }));\nconsole.log(parseHikeParams({ id: 'abc' }));\n"},"checks":[{"id":"valid","test":"return typeof parseHikeParams === 'function' && (() => { const r = parseHikeParams({ id: '42', units: 'mi', photo: '2' }); return r.id === 42 && r.units === 'mi' && r.photo === 2; })();"},{"id":"bad-id","test":"return (() => { const r = parseHikeParams({ id: 'abc' }); return r.id === null && r.units === 'km' && r.photo === 0; })() && parseHikeParams({ id: '0' }).id === null && parseHikeParams({ id: '4.5' }).id === null && parseHikeParams({ id: '-3' }).id === null && parseHikeParams({}).id === null;"},{"id":"units","test":"return parseHikeParams({ id: '1', units: 'MI' }).units === 'mi' && parseHikeParams({ id: '1', units: 'miles' }).units === 'km' && parseHikeParams({ id: '1' }).units === 'km';"},{"id":"photo","test":"return parseHikeParams({ id: '1', photo: '0' }).photo === 0 && parseHikeParams({ id: '1', photo: '3' }).photo === 3 && parseHikeParams({ id: '1', photo: 'x' }).photo === 0 && parseHikeParams({ id: '1', photo: '-2' }).photo === 0;"},{"id":"arrays","test":"const r = parseHikeParams({ id: ['7', '8'], units: ['mi', 'km'], photo: ['1'] }); return r.id === 7 && r.units === 'mi' && r.photo === 1;"}],"title":"اقرأ الـ params في المسار بأمان","prompt":"<p>يمكن فتح شاشة التفاصيل في Trailhead على <code dir=\"ltr\">/hikes/[id]</code> من داخل التطبيق، أو من إشعار، أو من رابط قديم يحوي أي شيء. اكتب <code dir=\"ltr\">parseHikeParams(params)</code> التي تأخذ الكائن القادم من <code dir=\"ltr\">useLocalSearchParams()</code> وتُرجع <code dir=\"ltr\">{ id, units, photo }</code>:</p>\n<ul>\n<li><code dir=\"ltr\">id</code>: <strong>عدد صحيح موجب</strong>، أو <code dir=\"ltr\">null</code> إن كان مفقودًا أو غير صالح (<code dir=\"ltr\">'abc'</code>، <code dir=\"ltr\">'0'</code>، <code dir=\"ltr\">'4.5'</code>، <code dir=\"ltr\">'-3'</code>).</li>\n<li><code dir=\"ltr\">units</code>: <code dir=\"ltr\">'mi'</code> إن كان الـ param يساوي <code dir=\"ltr\">mi</code> بأي حالة أحرف؛ وإلا <code dir=\"ltr\">'km'</code>.</li>\n<li><code dir=\"ltr\">photo</code>: عدد صحيح يساوي 0 أو أكثر (أيّ صورة تُعرض أولًا)؛ و<code dir=\"ltr\">0</code> إن كان مفقودًا أو غير صالح.</li>\n</ul>\n<p>قد يصل أي param أيضًا <strong>مصفوفةً</strong> من النصوص (مفتاح query مكرّر). استخدم القيمة الأولى.</p>\n","hints":["اكتب دالة مساعدة صغيرة أولًا: <code dir=\"ltr\">const first = (v) =&gt; Array.isArray(v) ? v[0] : v;</code>.","<code dir=\"ltr\">/^\\d+$/</code> لا يقبل إلا الأرقام، فيرفض <code dir=\"ltr\">-3</code> و<code dir=\"ltr\">4.5</code> و<code dir=\"ltr\">abc</code> في اختبار واحد. ثم تحقّق أن الرقم أكبر من 0 في حالة المعرّف.","للوحدات، حوّل النص إلى أحرف صغيرة وقارنه بـ <code dir=\"ltr\">'mi'</code>؛ وكل ما عداه يعود إلى <code dir=\"ltr\">'km'</code>."],"explanation":"<p>الـ params تأتي من URL، فهي نصوص أو مصفوفات من النصوص، لا أرقام أبدًا، مهما قال الـ generic في TypeScript. توحيدها في دالة نقية واحدة يعطي الشاشة أنواعًا حقيقية وقيمًا افتراضية آمنة، ويحوّل الـ deep link المعطوب إلى <code dir=\"ltr\">id: null</code>، فتعرض الشاشة رسالة ودودة «الرحلة غير موجودة» بدلًا من الانهيار. والتعبير النمطي الذي يقبل الأرقام فقط أكثر صرامة من <code dir=\"ltr\">Number()</code>، التي تقبل بسرور <code dir=\"ltr\">'4.5'</code> و<code dir=\"ltr\">' 7 '</code> و<code dir=\"ltr\">''</code> (بوصفها 0).</p>\n","checkLabels":{"valid":"مجموعة params عادية تُحوَّل إلى أنواع حقيقية","bad-id":"المعرّفات غير الصالحة أو المفقودة تصبح null","units":"الوحدات لا تتأثّر بحالة الأحرف وقيمتها الافتراضية km","photo":"فهرس الصورة يساوي 0 افتراضيًا للقيم المفقودة أو غير الصالحة","arrays":"الـ params التي تصل مصفوفات تستخدم قيمتها الأولى"}},"words":591,"readMinutes":3}