{"id":"l-at-3-4","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"ابنِ أنواعًا نصية من أنواع أخرى بالـ template literals، وحوّلها بـ Uppercase وCapitalize، وحلّل أنماط المسارات بـ infer لتحديد أنواع معاملات المسار.","html":"<p>يُطلق نظام الدفع في Cartwheel أحداثًا مثل <code dir=\"ltr\">order.shipped</code> و<code dir=\"ltr\">payment.failed</code>، ويبني عميل الـ API فيه الروابط من أنماط مثل <code dir=\"ltr\">/orders/:orderId/lines/:lineId</code>. كلاهما نصوص لها بنية. وتحديد نوعها بـ <code dir=\"ltr\">string</code> يعني أن الخطأ الإملائي في اسم حدث أو معامل المسار الناقص لا يظهر إلا حين لا يُطلَق listener أبدًا أو يُعيد طلبٌ 404. الـ template literal types تتيح للـ compiler أن يرى البنية.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">بناء النصوص من الأنواع</h2>\n<p>الـ template literal type يستخدم صياغة علامات الاقتباس المائلة نفسها في نصوص JavaScript القالبية، لكن مع أنواع في العناصر النائبة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderStatus</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'processing'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'shipped'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'delivered'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'returned'</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\"> OrderEvent</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `order.${</span><span style=\"color:#953800;--shiki-dark:#FFA657\">OrderStatus</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'order.processing' | 'order.shipped' | 'order.delivered' | 'order.cancelled' | 'order.returned'</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\"> CheckoutEvent</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderEvent</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `payment.${'captured'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'failed'}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p>حين يحمل عنصر نائب union، يكون للنتيجة عضو واحد لكل عضو في الـ union. ومع عنصرين نائبين تحصل على كل التركيبات: <code dir=\"ltr\">`${'s' | 'm' | 'l'}-${'red' | 'blue'}`</code> هي ستة نصوص. هذه قوة الميزة وخطرها في آنٍ، كما سترى في نهاية الدرس.</p>\n<p>الآن يستطيع ناقل الأحداث تحديد أنواع أسماء أحداثه من أنواع المجال التي تملكها أصلًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</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\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">event</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutEvent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">handler</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:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'order.shipped'</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:#8250DF;--shiki-dark:#D2A8FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'order.shiped'</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:#6E7781;--shiki-dark:#8B949E\">// Error: Argument of type '\"order.shiped\"' is not assignable to parameter of type 'CheckoutEvent'.</span></span></code></pre></div>\n<p>أضف حالة إلى <code dir=\"ltr\">OrderStatus</code> فيصبح <code dir=\"ltr\">order.on-hold</code> اسم حدث صالحًا تلقائيًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">تحويل النصوص</h2>\n<p>يأتي TypeScript بأربعة أنواع نصية <strong>مدمجة (intrinsic)</strong> تحوّل الأنواع الحرفية: <code dir=\"ltr\">Uppercase</code> و<code dir=\"ltr\">Lowercase</code> و<code dir=\"ltr\">Capitalize</code> و<code dir=\"ltr\">Uncapitalize</code>. استخدمت <code dir=\"ltr\">Capitalize</code> لأسماء الـ setters في <a href=\"/ar/lesson/advanced-typescript/l-at-3-2\">الـ Mapped Types وإعادة تسمية المفاتيح</a>. واستخدام شائع آخر هو متغيّرات البيئة وأعلام الميزات:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Feature</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'express-shipping'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'gift-wrap'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FlagEnvVar</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `CHECKOUT_${</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Uppercase</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Feature</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'CHECKOUT_EXPRESS-SHIPPING' | 'CHECKOUT_GIFT-WRAP'</span></span></code></pre></div>\n<p>إنها مبنية داخل الـ compiler لا معرَّفة بلغة TypeScript، فلا يمكنك رؤية مصدرها، لكنها تتصرّف مثل أي نوع generic آخر.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">تحليل النصوص بـ infer</h2>\n<p>تعمل الـ template literal types أيضًا كـ <strong>أنماط</strong> في conditional type. ومع <code dir=\"ltr\">infer</code>، تستخرج أجزاءً من literal type نصي:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Method</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">R</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> R</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> M</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} ${</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> M</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</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\"> M1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Method</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'POST /orders/:id/cancel'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'POST'</span></span></code></pre></div>\n<p>النمط <code dir=\"ltr\">`${infer M} ${string}`</code> يعني “نصٌّ ما، ثم مسافة، ثم أي شيء”. يطابقه TypeScript مع القيمة الحرفية ويربط <code dir=\"ltr\">M</code> بالجزء الذي قبل أول مسافة.</p>\n<p>إليك النوع الذي يجعل باني الروابط آمنًا. إنه يجمع كل <code dir=\"ltr\">:param</code> في نمط المسار:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PathParams</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}:${</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Param</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}/${</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Rest</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Param</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PathParams</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Rest</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:#953800;--shiki-dark:#FFA657\"> P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}:${</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Param</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Param</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</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\"> P1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PathParams</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'/orders/:orderId/lines/:lineId'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'orderId' | 'lineId'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> P2</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PathParams</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'/orders'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;                        </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// never</span></span></code></pre></div>\n<p>اقرأه كحالتين. إن كان هناك معامل يليه مزيد من المسار، التقط المعامل وعاوِد على الباقي. وإن كان هناك معامل في النهاية، التقطه. وإلا فلا معاملات. حين يطابق TypeScript نمطًا، يأخذ كل عنصر نائب <code dir=\"ltr\">infer</code> أقصر نص يسمح لبقية النمط بالمطابقة، فيتوقّف <code dir=\"ltr\">Param</code> عند أول <code dir=\"ltr\">/</code>.</p>\n<p>التعاود (<code dir=\"ltr\">PathParams&lt;Rest&gt;</code>) هو موضوع الدرس القادم. أما الآن فانظر إلى ما يعطيك إياه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> buildPath</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</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\">pattern</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> P</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">params</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Record</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">PathParams</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</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:#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:#6E7781;--shiki-dark:#8B949E\">  // Every :param in the pattern has a key in params, by construction.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> values</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> params </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Record</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</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\"> pattern.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">replace</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/:(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">\\w</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)/</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">g</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">_</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:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> encodeURIComponent</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(values[name])));</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:#8250DF;--shiki-dark:#D2A8FF\">buildPath</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'/orders/:orderId/lines/:lineId'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, { orderId: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1042</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, lineId: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// '/orders/1042/lines/3'</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">buildPath</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'/orders/:orderId/lines/:lineId'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, { orderId: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1042</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: … Property 'lineId' is missing in type '{ orderId: number; }' …</span></span></code></pre></div>\n<p>يُستنتج <code dir=\"ltr\">P</code> على أنه النمط الحرفي، ويحوّله <code dir=\"ltr\">PathParams&lt;P&gt;</code> إلى المفاتيح المطلوبة، ويحوّل <code dir=\"ltr\">Record</code> تلك المفاتيح إلى شكل <code dir=\"ltr\">params</code>. والمسار الذي لا معاملات فيه يأخذ <code dir=\"ltr\">{}</code>.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ template literals كمفاتيح</h2>\n<p>أسماء الأحداث نصف القصة فقط؛ كل حدث يحمل أيضًا حمولة. يستطيع mapped type مع جملة <code dir=\"ltr\">as</code> بناء جدول الاسم إلى الحمولة كاملًا من union الحالات دفعة واحدة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"6\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderEventPayloads</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:#953800;--shiki-dark:#FFA657\">S</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderStatus</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> as</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `order.${</span><span style=\"color:#953800;--shiki-dark:#FFA657\">S</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</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\">orderId</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\">status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> S</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippedPayload</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderEventPayloads</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'order.shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { orderId: number; status: 'shipped' }</span></span></code></pre></div>\n<p>تمرّ الحلقة على الحالات، وتحوّل جملة <code dir=\"ltr\">as</code> كل واحدة منها إلى اسم حدث، ويظل نوع الحمولة قادرًا على الإشارة إلى <code dir=\"ltr\">S</code> الأصلي. هذا الجدول هو بالضبط ما يحتاجه event emitter محدَّد الأنواع، وستبني واحدًا فوقه في <a href=\"/ar/lesson/advanced-typescript/l-at-4-4\">باعثات أحداث وبنّاؤون آمنون من حيث النوع</a>. لاحظ أنه لا شيء جديد هنا: mapped type وtemplate literal ووصول بالفهرس، مجتمعة. معظم كود مستوى الأنواع الحقيقي قطع صغيرة كهذه، مركّبة بعناية.</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>عناصر نائبة فضفاضة</span></p>\n<p>العنصران النائبان <code dir=\"ltr\">${string}</code> و<code dir=\"ltr\">${number}</code> يطابقان على نطاق واسع. <code dir=\"ltr\">`${number}`</code> تقبل <code dir=\"ltr\">'12.50'</code>، لكنها تقبل أيضًا <code dir=\"ltr\">'1e5'</code> و<code dir=\"ltr\">'-0'</code>، لأنها تعني “أي شيء تقرؤه JavaScript كرقم”. أنواع مثل <code dir=\"ltr\">`${Category}-${string}`</code> لرموز المنتجات (SKUs) توثيق مفيد وتلتقط الأخطاء الواضحة، لكنها ليست تحقّقًا؛ افحص المدخلات الحقيقية وقت التشغيل.</p>\n</aside>\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>مضاعفة الـ unions حتى يستسلم الـ compiler</span></p>\n<p>كل عنصر نائب يضاعف حجم الـ union. أربعة عناصر نائبة فيها 40 و30 و15 و500 عضو ستكون تسعة ملايين نص، ويتوقّف TypeScript عند 100,000 برسالة <em>Expression produces a union type that is too complex to represent</em>. وقبل ذلك الحد بكثير تصبح المحرّرات بطيئة. استخدم الـ template literals مع unions صغيرة ومغلقة (الحالات، والطرق، وبضع لغات)، واستخدم <code dir=\"ltr\">string</code> لأي شيء كثير القيم، مثل رموز المنتجات أو المعرّفات.</p>\n</aside>\n<p>في التمرين ستحدّد أنواع أسماء الأحداث وباني الروابط في Cartwheel. والدرس القادم ينظر في التعاود داخل الأنواع: ما فائدته، وإلى أي عمق يمكن أن يصل، ومتى يكون النوع الأبسط هو الخيار الهندسي الأفضل.</p>\n","toc":[{"id":"sec-1","text":"بناء النصوص من الأنواع","level":2},{"id":"sec-2","text":"تحويل النصوص","level":2},{"id":"sec-3","text":"تحليل النصوص بـ infer","level":2},{"id":"sec-4","text":"الـ template literals كمفاتيح","level":2}],"takeaways":["الـ template literal type مثل <code dir=\"ltr\">`order.${OrderStatus}`</code> يبني literal types نصية، والـ union داخله ينتج كل التركيبات.","<code dir=\"ltr\">Uppercase</code> و<code dir=\"ltr\">Lowercase</code> و<code dir=\"ltr\">Capitalize</code> و<code dir=\"ltr\">Uncapitalize</code> أنواع مدمجة تحوّل الـ literal types النصية.","<code dir=\"ltr\">infer</code> داخل نمط template literal تلتقط أجزاءً من النص، ما يتيح لك تحليل أنماط مسارات مثل <code dir=\"ltr\">/orders/:orderId</code> على مستوى الأنواع.","العناصر النائبة <code dir=\"ltr\">${number}</code> و<code dir=\"ltr\">${string}</code> تقبل أنماطًا فضفاضة؛ توثّق صيغة لكنها لا تتحقّق منها بصرامة.","الـ unions داخل الـ template literals تتضاعف، ويرفض TypeScript الـ unions التي تتجاوز 100,000 عضو، فأبقِ المدخلات صغيرة."],"further":[{"title":"Template Literal Types","url":"https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html"},{"title":"Intrinsic String Manipulation Types","url":"https://www.typescriptlang.org/docs/handbook/utility-types.html#intrinsic-string-manipulation-types"}],"quiz":[{"q":"<p>كم عضوًا في <code dir=\"ltr\">`${'s' | 'm' | 'l'}-${'red' | 'blue'}`</code>؟</p>","options":[{"text":"5، واحد لكل قيمة حرفية","why":"الـ unions داخل الـ template literal تتركّب ولا تُجمع. كل مقاس يقترن بكل لون."},{"text":"6، كل مقاس مقترن بكل لون","why":"صحيح. النتيجة هي الجداء الديكارتي، <code dir=\"ltr\">'s-red' | 's-blue' | 'm-red' | …</code>، فتتضاعف المقاسات."},{"text":"1، نص النمط نفسه","why":"الـ template literal types التي فيها unions حرفية تُوسَّع إلى نصوص محدّدة، ولا تبقى نمطًا واحدًا."}],"answer":1},{"q":"<p>ماذا يعطي <code dir=\"ltr\">PathParams&lt;'/orders/:orderId/lines/:lineId'&gt;</code> بالتعريف التعاودي من الدرس؟</p>","options":[{"text":"<code dir=\"ltr\">'orderId' | 'lineId'</code>","why":"صحيح. المطابقة الأولى تلتقط <code dir=\"ltr\">orderId</code> وتعاود على <code dir=\"ltr\">lines/:lineId</code>، التي تلتقط مطابقتها الأخيرة <code dir=\"ltr\">lineId</code>."},{"text":"<code dir=\"ltr\">':orderId' | ':lineId'</code>","why":"النقطتان جزء من النمط، خارج العنصر النائب <code dir=\"ltr\">infer</code>، فلا تُلتقطان."},{"text":"<code dir=\"ltr\">'orderId'</code> فقط","why":"هذا ما تحصل عليه دون الاستدعاء التعاودي على <code dir=\"ltr\">Rest</code>؛ التعاود هو ما يصل إلى المعامل الثاني."},{"text":"<code dir=\"ltr\">string</code>","why":"<code dir=\"ltr\">infer</code> تلتقط الأجزاء النصية الحرفية بالضبط، فتكون النتيجة union من القيم الحرفية."}],"answer":0},{"q":"<p>أيّ قيمة يقبلها <code dir=\"ltr\">type Price = </code>${number}``؟</p>","options":[{"text":"فقط النصوص مثل <code dir=\"ltr\">'12.50'</code> التي تبدو كمبالغ مالية","why":"<code dir=\"ltr\">${number}</code> لا تعرف شيئًا عن صيغ المبالغ؛ تقبل أي شيء تقرؤه JavaScript كرقم."},{"text":"<code dir=\"ltr\">'1e5'</code> وكذلك <code dir=\"ltr\">'12.50'</code>","why":"صحيح. أي نص تقرؤه JavaScript كرقم يناسب، بما في ذلك الصيغة الأُسّية (بل حتى <code dir=\"ltr\">'0x1F'</code>)، فهو فحص فضفاض."},{"text":"الأعداد الصحيحة فقط، لأن <code dir=\"ltr\">number</code> غير مسموح في الـ template literals","why":"العناصر النائبة <code dir=\"ltr\">number</code> مسموحة وتقبل الكسور العشرية أيضًا."}],"answer":1},{"q":"<p>يبني زميل <code dir=\"ltr\">`${Locale}/${Currency}/${Category}/${Sku}`</code> من أربعة unions فيها 40 و30 و15 و500 عضو. ماذا يحدث؟</p>","options":[{"text":"يعمل، لكن الإكمال التلقائي يصبح أبطأ.","why":"الجداء 9 ملايين عضو، أبعد بكثير من الحد؛ الأمر ليس مجرّد بطء."},{"text":"يُبقيه TypeScript نمطًا غير موسَّع.","why":"الـ unions الحرفية تُوسَّع دائمًا؛ لا يوجد وضع كسول لها."},{"text":"يبلّغ TypeScript عن “Expression produces a union type that is too complex to represent”.","why":"صحيح. الجداء يتجاوز حد 100,000 عضو. استخدم <code dir=\"ltr\">string</code> للجزء كثير القيم، أو تحقّق وقت التشغيل."}],"answer":2}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype PaymentOutcome = 'captured' | 'failed';\ntype Feature = 'express-shipping' | 'gift-wrap';\n\ntype CheckoutEvent = string;\ntype FlagEnvVar = string;\ntype PathParams<P extends string> = string;\n\nfunction buildPath<P extends string>(pattern: P, params: Record<PathParams<P>, string | number>): string {\n  return pattern;\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<CheckoutEvent,\n  | 'order.processing' | 'order.shipped' | 'order.delivered' | 'order.cancelled' | 'order.returned'\n  | 'payment.captured' | 'payment.failed'>>;\ntype T2 = Expect<Equal<FlagEnvVar, 'CHECKOUT_EXPRESS-SHIPPING' | 'CHECKOUT_GIFT-WRAP'>>;\ntype T3 = Expect<Equal<PathParams<'/orders/:orderId/lines/:lineId'>, 'orderId' | 'lineId'>>;\ntype T4 = Expect<Equal<PathParams<'/customers/:customerId'>, 'customerId'>>;\ntype T5 = Expect<Equal<PathParams<'/orders'>, never>>;\n\n// @ts-expect-error: 'order.shiped' is a typo\nconst typo: CheckoutEvent = 'order.shiped';\n\n// @ts-expect-error: lineId is missing\nbuildPath('/orders/:orderId/lines/:lineId', { orderId: 1042 });\n\nconsole.log(buildPath('/orders/:orderId/lines/:lineId', { orderId: 1042, lineId: 3 }));\n"},"solution":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype PaymentOutcome = 'captured' | 'failed';\ntype Feature = 'express-shipping' | 'gift-wrap';\n\ntype CheckoutEvent = `order.${OrderStatus}` | `payment.${PaymentOutcome}`;\ntype FlagEnvVar = `CHECKOUT_${Uppercase<Feature>}`;\ntype PathParams<P extends string> =\n  P extends `${string}:${infer Param}/${infer Rest}`\n    ? Param | PathParams<Rest>\n    : P extends `${string}:${infer Param}`\n      ? Param\n      : never;\n\nfunction buildPath<P extends string>(pattern: P, params: Record<PathParams<P>, string | number>): string {\n  // Every :param in the pattern has a key in params, by construction.\n  const values = params as Record<string, string | number>;\n  return pattern.replace(/:(\\w+)/g, (_, name: string) => encodeURIComponent(String(values[name])));\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<CheckoutEvent,\n  | 'order.processing' | 'order.shipped' | 'order.delivered' | 'order.cancelled' | 'order.returned'\n  | 'payment.captured' | 'payment.failed'>>;\ntype T2 = Expect<Equal<FlagEnvVar, 'CHECKOUT_EXPRESS-SHIPPING' | 'CHECKOUT_GIFT-WRAP'>>;\ntype T3 = Expect<Equal<PathParams<'/orders/:orderId/lines/:lineId'>, 'orderId' | 'lineId'>>;\ntype T4 = Expect<Equal<PathParams<'/customers/:customerId'>, 'customerId'>>;\ntype T5 = Expect<Equal<PathParams<'/orders'>, never>>;\n\n// @ts-expect-error: 'order.shiped' is a typo\nconst typo: CheckoutEvent = 'order.shiped';\n\n// @ts-expect-error: lineId is missing\nbuildPath('/orders/:orderId/lines/:lineId', { orderId: 1042 });\n\nconsole.log(buildPath('/orders/:orderId/lines/:lineId', { orderId: 1042, lineId: 3 }));\n"},"checks":[{"id":"types","typecheck":true},{"id":"builds","test":"return buildPath('/orders/:orderId/lines/:lineId', { orderId: 1042, lineId: 3 }) === '/orders/1042/lines/3' && buildPath('/orders', {}) === '/orders';"},{"id":"encodes","test":"return buildPath('/search/:q', { q: 'tent & tarp' }) === '/search/tent%20%26%20tarp';"}],"title":"حدّد أنواع أسماء الأحداث وأنماط الروابط","prompt":"<p>استخدم الـ template literal types لتحديد أنواع نصوص Cartwheel:</p>\n<ul>\n<li><code dir=\"ltr\">CheckoutEvent</code>: <code dir=\"ltr\">order.&lt;status&gt;</code> لكل <code dir=\"ltr\">OrderStatus</code>، إضافة إلى <code dir=\"ltr\">payment.&lt;outcome&gt;</code> لكل <code dir=\"ltr\">PaymentOutcome</code>.</li>\n<li><code dir=\"ltr\">FlagEnvVar</code>: <code dir=\"ltr\">CHECKOUT_</code> متبوعة بكل <code dir=\"ltr\">Feature</code> بأحرف كبيرة.</li>\n<li><code dir=\"ltr\">PathParams&lt;P&gt;</code>: أسماء كل <code dir=\"ltr\">:param</code> في نمط المسار (<code dir=\"ltr\">never</code> حين لا يوجد أيٌّ منها).</li>\n</ul>\n<p>ثم نفّذ <code dir=\"ltr\">buildPath(pattern, params)</code> وقت التشغيل: استبدل كل <code dir=\"ltr\">:param</code> بقيمته من <code dir=\"ltr\">params</code>، مرمّزة للروابط بـ <code dir=\"ltr\">encodeURIComponent</code>.</p>\n","hints":["<code dir=\"ltr\">`order.${OrderStatus}`</code> يتوسّع إلى نص واحد لكل حالة. و<code dir=\"ltr\">Uppercase&lt;Feature&gt;</code> تعمل داخل العنصر النائب.","لـ <code dir=\"ltr\">PathParams</code> حالتان: معامل يليه <code dir=\"ltr\">/</code> ومزيد من المسار (التقطه وعاوِد على الباقي)، أو معامل في النهاية تمامًا.","وقت التشغيل، تزور <code dir=\"ltr\">pattern.replace(/:(\\w+)/g, (_, name) =&gt; …)</code> كل اسم معامل. عامل <code dir=\"ltr\">params</code> في الداخل كـ <code dir=\"ltr\">Record&lt;string, string | number&gt;</code> بتحويل واحد مشروح بتعليق."],"explanation":"<p>الـ union داخل الـ template literal يتوسّع إلى نص واحد لكل عضو، فتُشتقّ عائلتا الأحداث كلتاهما من الـ unions الموجودة. وتحوّل <code dir=\"ltr\">Uppercase</code> كل اسم ميزة. أما <code dir=\"ltr\">PathParams</code> فتطابق المسار بالأنماط: تلتقط <code dir=\"ltr\">infer Param</code> النص الذي بعد النقطتين حتى أول <code dir=\"ltr\">/</code>، ويعالج الاستدعاء التعاودي بقية المسار؛ ويلتقط الفرع الأخير المعامل الذي في النهاية. ثم يجعل <code dir=\"ltr\">Record&lt;PathParams&lt;P&gt;, …&gt;</code> كل معامل مفتاحًا إلزاميًا في <code dir=\"ltr\">params</code>، فيكون <code dir=\"ltr\">lineId</code> الناقص خطأ ترجمة لا طلبًا إلى <code dir=\"ltr\">/orders/1042/lines/undefined</code>.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ، بما فيها كل اختبارات الأنواع","builds":"<code dir=\"ltr\">buildPath</code> تملأ كل معامل وتترك المسارات الخالية من المعاملات كما هي","encodes":"قيم المعاملات مرمّزة للروابط"}},"words":618,"readMinutes":4}