{"id":"l-at-2-3","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"احجب مواقع الاستنتاج غير المرغوبة بـ NoInfer، وأعطِ معاملات النوع قيمًا افتراضية معقولة، واختر بين الـ overloads والـ unions والدوال المنفصلة حين يعتمد نوع الإرجاع على المدخل.","html":"<p>إليك دالة مساعدة من نظام الدفع في Cartwheel. تبدو آمنة، وفيها ثغرة:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">C</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\">available</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">code</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …</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\">applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOME10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'LOYAL5'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOM10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// compiles</span></span></code></pre></div>\n<p>النية واضحة: يجب أن يكون <code dir=\"ltr\">code</code> واحدًا من الكوبونات المتاحة. لكن <code dir=\"ltr\">C</code> يظهر في المعاملين كليهما، وكما رأيت في الدرس السابق، كل ظهور موقعُ استنتاج. يجمع TypeScript القيم <code dir=\"ltr\">'WELCOME10'</code> و<code dir=\"ltr\">'LOYAL5'</code> و<code dir=\"ltr\">'WELCOM10'</code> كمرشّحين، ويجعل <code dir=\"ltr\">C</code> هو الـ union من الثلاثة، فيجتاز كل شيء فحص الأنواع. لم يُفحص الخطأ الإملائي مقابل القائمة؛ بل أُضيف إليها.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">NoInfer: افحص هنا، لكن لا تستنتج من هنا</h2>\n<p>أضاف TypeScript 5.4 النوع المدمج <code dir=\"ltr\">NoInfer&lt;T&gt;</code>. يُحَلّ إلى <code dir=\"ltr\">T</code> العادي، لكنه يجعل ذلك الموضع محظورًا على الـ inference:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">C</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\">available</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">code</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> NoInfer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …</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\">applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOME10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'LOYAL5'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOM10'</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 '\"WELCOM10\"' is not assignable to parameter of type '\"WELCOME10\" | \"LOYAL5\"'.</span></span></code></pre></div>\n<p>الآن يُستنتج <code dir=\"ltr\">C</code> من <code dir=\"ltr\">available</code> وحدها، ويُفحص <code dir=\"ltr\">code</code> مقابل النتيجة. يظهر هذا النمط كلما كان أحد الوسائط يعرّف مجموعة وعلى وسيط آخر أن يختار منها: حالة ابتدائية يجب أن تكون إحدى الحالات المعرَّفة، أو قيمة افتراضية يجب أن تطابق الخيارات، أو مفتاح ترتيب يجب أن يكون أحد الأعمدة المدرَجة.</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> createMachine</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">S</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\">config</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">states</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 style=\"color:#953800;--shiki-dark:#FFA657\">initial</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> NoInfer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> config;</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\">createMachine</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ states: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cart'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipping'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'payment'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], initial: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cart'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ok</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">createMachine</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ states: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cart'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipping'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'payment'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], initial: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'checkout'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// error</span></span></code></pre></div>\n<p>قبل 5.4، كان الحل البديل معامل نوع ثانيًا مقيَّدًا بالأول، <code dir=\"ltr\">&lt;C extends string, D extends C&gt;(available: C[], code: D)</code>. هذا يعمل، وستراه في المكتبات القديمة، لكنه يضيف معاملًا لا وجود له إلا للتهرّب من الاستنتاج. استخدم <code dir=\"ltr\">NoInfer</code> في الكود الجديد. فهي توثّق النية أيضًا: القارئ الذي يرى <code dir=\"ltr\">initial: NoInfer&lt;S&gt;</code> يعرف من النظرة الأولى أيّ وسيط هو مصدر الحقيقة وأيّها يُفحص فقط، وهذا ما لم توضّحه حيلة المعاملين أبدًا.</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>تغليف الموضع الخطأ</span></p>\n<p>توضع <code dir=\"ltr\">NoInfer</code> على الموضع الذي يجب أن <em>يُفحص</em>، لا على الموضع الذي يعرّف النوع. اكتب <code dir=\"ltr\">available: NoInfer&lt;C&gt;[]</code> بدلًا من ذلك وسيُستنتج <code dir=\"ltr\">C</code> من الرمز المفرد وحده، فتُفحص القائمة مقابل الخطأ الإملائي. اسأل “أيّ وسيط هو مصدر الحقيقة؟” واترك ذلك الوسيط دون تغليف.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">قيم افتراضية لمعاملات النوع</h2>\n<p>يمكن أن تكون لمعامل النوع قيمة افتراضية، تُستخدم حين لا يوجد ما يُستنتج منه:</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\"> ApiResponse</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unknown</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\">data</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">requestId</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> request</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">TRes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">TBody</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">path</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\">body</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> TBody</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">TRes</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\"> res</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> fetch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(path, { method: body </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'GET'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'POST'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, body: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">JSON</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">stringify</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(body) });</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> res.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">json</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\">ApiResponse</code> وحدها تعني الآن <code dir=\"ltr\">ApiResponse&lt;unknown&gt;</code>، وهي قيمة افتراضية آمنة: على المستدعين تضييق <code dir=\"ltr\">data</code> قبل استخدامها. ثانيًا، القيم الافتراضية تجعل <strong>وسائط النوع الصريحة الجزئية</strong> مشروعة. دونها كانت <code dir=\"ltr\">request&lt;Order&gt;('/orders/1042')</code> ستفشل مع “Expected 2 type arguments”؛ ومعها يرجع <code dir=\"ltr\">TBody</code> إلى <code dir=\"ltr\">undefined</code>.</p>\n<p>اختر قيمًا افتراضية آمنة لا مريحة. <code dir=\"ltr\">unknown</code> قيمة افتراضية جيدة للبيانات القادمة من الخارج؛ أما <code dir=\"ltr\">any</code> فلا، لأنها تُطفئ الفحص بصمت لكل من ينسى وسيط النوع. (ولاحظ أن <code dir=\"ltr\">request&lt;Order&gt;</code> ما زالت تحويلًا متنكّرًا: جسم الاستجابة لا يُفحص. ستُصلح ذلك كما ينبغي في الدرس القادم وفي <a href=\"/ar/lesson/advanced-typescript/l-at-4-3\">تحليل البيانات غير الموثوقة عند الحدود</a>.)</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">حين يعتمد نوع الإرجاع على الوسيط</h2>\n<p>أحيانًا يحدّد <em>نوعُ</em> الوسيط نوعَ النتيجة. الـ overloads (التحميل الزائد) تصف ذلك مباشرة:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</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:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</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:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</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:#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\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Array.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">isArray</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(cents) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cents.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(toDollars) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> toDollars</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(cents);</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> toDollars</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</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:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `$${</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> /</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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:#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\"> one</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// string</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> many</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1299</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// string[]</span></span></code></pre></div>\n<p>السطران الأولان هما <strong>توقيعا الـ overload</strong> اللذان يراهما المستدعون. والثالث هو <strong>توقيع التنفيذ</strong>، الذي يجب أن يتوافق معها جميعًا وهو غير مرئي من الخارج. يجرّب TypeScript الـ overloads من الأعلى إلى الأسفل ويستخدم أول واحد يطابق، فضع الأكثر تحديدًا أولًا.</p>\n<p>للـ overloads حافة حادة واحدة: المستدعي الذي بين يديه <code dir=\"ltr\">number | number[]</code> لا يطابق <em>أيًّا</em> من الـ overloads، فيحصل على “No overload matches this call”. ستحتاج إلى overload ثالث يقبل الـ union. وهذه الحافة تلميح بشأن التصميم. إذا كان لدى المستدعين الـ union غالبًا، فالدالة تريد على الأرجح توقيعًا واحدًا؛ وإن كان نادرًا، فدالتان منفصلتان باسمين واضحين (<code dir=\"ltr\">formatPrice</code> و<code dir=\"ltr\">formatPrices</code>) تتفوّقان عادةً على دالة واحدة محمّلة بالـ overloads.</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>قاعدة افتراضية لتصميم الـ API</span></p>\n<p>فضّل بالترتيب: دوال منفصلة بأسماء جيدة؛ ثم توقيعًا واحدًا بـ union حيث لا يعتمد نوع الإرجاع على المدخل؛ ثم الـ overloads حين تحدّد أنواع API موجود في JavaScript أو حين تكون الراحة مهمة فعلًا. أنواع الإرجاع الشرطية على الدوال الـ generic تأتي أخيرًا؛ فهي تحتاج عادةً إلى تحويلات داخل التنفيذ، وسترى ذلك في القسم 3.</p>\n</aside>\n<p>في التمرين ستُصلح ثلاثًا من مشكلات الـ inference هذه في نظام الدفع في Cartwheel. والدرس القادم يجمع كل ما في هذا القسم في عميل API واحد محدَّد الأنواع.</p>\n","toc":[{"id":"sec-1","text":"NoInfer: افحص هنا، لكن لا تستنتج من هنا","level":2},{"id":"sec-2","text":"قيم افتراضية لمعاملات النوع","level":2},{"id":"sec-3","text":"حين يعتمد نوع الإرجاع على الوسيط","level":2}],"takeaways":["كل ظهور لمعامل النوع موقعُ استنتاج، فقد يؤدّي خطأ إملائي في وسيط واحد إلى توسيع النوع بدل رفضه.","<code dir=\"ltr\">NoInfer&lt;T&gt;</code> (منذ TypeScript 5.4) تجعل الموضع للفحص فقط: يُتحقَّق منه مقابل <code dir=\"ltr\">T</code> لكنه لا يساهم بأي مرشّح.","القيم الافتراضية لمعاملات النوع تُطبَّق حين لا يوجد ما يُستنتج منه، وتجعل وسائط النوع الصريحة الجزئية ممكنة.","الـ overloads تتيح لنوع الإرجاع أن يعتمد على نوع الوسيط، لكن الوسيط من نوع union لا يطابق أيًّا منها؛ ودالتان بأسماء جيدة كثيرًا ما تكونان أوضح."],"further":[{"title":"NoInfer (TypeScript 5.4 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html"},{"title":"Function overloads","url":"https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads"},{"title":"NoInfer utility type","url":"https://www.typescriptlang.org/docs/handbook/utility-types.html#noinfertype"}],"quiz":[{"q":"<p>لماذا يمرّ هذا الاستدعاء في الترجمة رغم أن <code dir=\"ltr\">'WELCOM10'</code> خطأ إملائي؟</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">C</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\">available</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">code</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {}</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOME10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'LOYAL5'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOM10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n","options":[{"text":"لأن القيم النصية الحرفية تتّسع دائمًا إلى <code dir=\"ltr\">string</code> في الاستدعاءات الـ generic.","why":"القيد <code dir=\"ltr\">extends string</code> يحتفظ بالقيم الحرفية. المشكلة في مصدر تلك القيم."},{"text":"لأن <code dir=\"ltr\">code</code> هو أيضًا موقع استنتاج، فيصبح <code dir=\"ltr\">C</code> هو <code dir=\"ltr\">'WELCOME10' | 'LOYAL5' | 'WELCOM10'</code>.","why":"صحيح. يُجمع الخطأ الإملائي كمرشّح بدل أن يُفحص. والحل <code dir=\"ltr\">code: NoInfer&lt;C&gt;</code>."},{"text":"لأن المصفوفات تُستنتج بعد الوسائط العادية.","why":"المصفوفات ليست حسّاسة للسياق؛ كلا الوسيطين يساهم بمرشّحين في الجولة نفسها."}],"answer":1},{"q":"<p><code dir=\"ltr\">function request&lt;TRes = unknown, TBody = undefined&gt;(path: string, body?: TBody): Promise&lt;TRes&gt;</code>. ماذا تفعل <code dir=\"ltr\">request&lt;Order&gt;('/orders/1')</code>؟</p>","options":[{"text":"خطأ، لأنه لم يُعطَ إلا واحد من وسيطَي النوع.","why":"تلك القاعدة تنطبق على معاملات النوع التي ليس لها قيم افتراضية. هنا لـ <code dir=\"ltr\">TBody</code> قيمة افتراضية، فيمكن حذفه."},{"text":"<code dir=\"ltr\">TRes</code> هو <code dir=\"ltr\">Order</code> و<code dir=\"ltr\">TBody</code> يُستنتج من <code dir=\"ltr\">body</code> الغائب.","why":"ما إن تمرّر وسائط نوع صريحة حتى يتوقّف الاستنتاج كليًا؛ والمحذوفة تأخذ قيمها الافتراضية."},{"text":"<code dir=\"ltr\">TRes</code> هو <code dir=\"ltr\">Order</code> و<code dir=\"ltr\">TBody</code> يرجع إلى قيمته الافتراضية <code dir=\"ltr\">undefined</code>.","why":"صحيح. القيم الافتراضية هي ما يجعل وسائط النوع الصريحة الجزئية مشروعة."}],"answer":2},{"q":"<p>مع هذه الـ overloads، ماذا يحدث في السطر الأخير؟</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</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\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ids</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:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]>;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">idOrIds</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</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:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]> { </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* … */</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> input</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</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:#8250DF;--shiki-dark:#D2A8FF\">getOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(input);</span></span></code></pre></div>\n","options":[{"text":"يُعيد <code dir=\"ltr\">Promise&lt;Order | Order[]&gt;</code> من توقيع التنفيذ.","why":"توقيع التنفيذ غير مرئي للمستدعين. لا يمكن مطابقة إلا توقيعات الـ overloads التي فوقه."},{"text":"خطأ، “No overload matches this call”، لأنه لا يوجد overload واحد يقبل الـ union.","why":"صحيح. تُجرَّب الـ overloads واحدًا تلو الآخر، ولا يقبل أيٌّ منها <code dir=\"ltr\">number | number[]</code>. ستحتاج إلى overload ثالث للـ union."},{"text":"يختار الـ overload الأول، لأن الـ overloads تُجرَّب بالترتيب.","why":"الترتيب يحدّد أيّ overload يفوز حين يتطابق أكثر من واحد. هنا لا يتطابق أيٌّ منها، لأن <code dir=\"ltr\">number[]</code> تفشل في الأول."}],"answer":1},{"q":"<p>تصمّم دالة بحث تُعيد طلبًا واحدًا لمعرّف، وقائمة لعميل. أيّ API هو الأسهل للمستدعين وللمسؤولين عن صيانة الكود؟</p>","options":[{"text":"دالتان، <code dir=\"ltr\">getOrder(id)</code> و<code dir=\"ltr\">getOrdersForCustomer(customerId)</code>، لكلٍّ منهما توقيع بسيط.","why":"صحيح. كل دالة تقول ما تفعله، ولا تحتاج إلى overloads أو conditional types، والـ inference فيها بديهي."},{"text":"دالة واحدة <code dir=\"ltr\">get(idOrCustomer)</code> بتوقيعَي overload.","why":"هذا يعمل لكنه يحشر نيّتين في اسم واحد، والمستدعون الذين لديهم مدخلات من نوع union سيصطدمون بـ “No overload matches”."},{"text":"دالة generic واحدة <code dir=\"ltr\">get&lt;T&gt;(key)</code> بنوع إرجاع شرطي مبني على <code dir=\"ltr\">T</code>.","why":"أنواع الإرجاع الشرطية تحتاج عادةً إلى تحويلات داخل التنفيذ، وموضع الاستدعاء أقل وضوحًا من دالة مسمّاة."}],"answer":0}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"function applyCoupon<C extends string>(available: C[], code: C): string {\n  return available.includes(code) ? `Applied ${code}` : `Unknown coupon ${code}`;\n}\n\nfunction createMachine<S extends string>(config: { states: S[]; initial: S }) {\n  return { states: config.states, initial: config.initial };\n}\n\nfunction formatPrice(cents: number | number[]): string | string[] {\n  return Array.isArray(cents) ? cents.map(toDollars) : toDollars(cents);\n}\n\nfunction toDollars(cents: number): string {\n  return `$${(cents / 100).toFixed(2)}`;\n}\n\n// ---- Type tests (do not edit) ----\napplyCoupon(['WELCOME10', 'LOYAL5'], 'LOYAL5');\n// @ts-expect-error: 'WELCOM10' is a typo, not an available coupon\napplyCoupon(['WELCOME10', 'LOYAL5'], 'WELCOM10');\n\nconst machine = createMachine({ states: ['cart', 'shipping', 'payment'], initial: 'cart' });\ntype T1 = Expect<Equal<typeof machine.initial, 'cart' | 'shipping' | 'payment'>>;\n// @ts-expect-error: 'checkout' is not one of the declared states\ncreateMachine({ states: ['cart', 'shipping', 'payment'], initial: 'checkout' });\n\nconst one = formatPrice(4500);\nconst many = formatPrice([4500, 1299]);\ntype T2 = Expect<Equal<typeof one, string>>;\ntype T3 = Expect<Equal<typeof many, string[]>>;\n\nconsole.log(applyCoupon(['WELCOME10'], 'WELCOME10'), machine.initial, one, many);\n"},"solution":{"ts":"function applyCoupon<C extends string>(available: C[], code: NoInfer<C>): string {\n  return available.includes(code) ? `Applied ${code}` : `Unknown coupon ${code}`;\n}\n\nfunction createMachine<S extends string>(config: { states: S[]; initial: NoInfer<S> }) {\n  return { states: config.states, initial: config.initial };\n}\n\nfunction formatPrice(cents: number): string;\nfunction formatPrice(cents: number[]): string[];\nfunction formatPrice(cents: number | number[]): string | string[] {\n  return Array.isArray(cents) ? cents.map(toDollars) : toDollars(cents);\n}\n\nfunction toDollars(cents: number): string {\n  return `$${(cents / 100).toFixed(2)}`;\n}\n\n// ---- Type tests (do not edit) ----\napplyCoupon(['WELCOME10', 'LOYAL5'], 'LOYAL5');\n// @ts-expect-error: 'WELCOM10' is a typo, not an available coupon\napplyCoupon(['WELCOME10', 'LOYAL5'], 'WELCOM10');\n\nconst machine = createMachine({ states: ['cart', 'shipping', 'payment'], initial: 'cart' });\ntype T1 = Expect<Equal<typeof machine.initial, 'cart' | 'shipping' | 'payment'>>;\n// @ts-expect-error: 'checkout' is not one of the declared states\ncreateMachine({ states: ['cart', 'shipping', 'payment'], initial: 'checkout' });\n\nconst one = formatPrice(4500);\nconst many = formatPrice([4500, 1299]);\ntype T2 = Expect<Equal<typeof one, string>>;\ntype T3 = Expect<Equal<typeof many, string[]>>;\n\nconsole.log(applyCoupon(['WELCOME10'], 'WELCOME10'), machine.initial, one, many);\n"},"checks":[{"id":"types","typecheck":true},{"id":"format","test":"return formatPrice(4500) === '$45.00' && formatPrice([1299, 5]).join() === '$12.99,$0.05';"},{"id":"coupon","test":"return applyCoupon(['A', 'B'], 'B') === 'Applied B';"}],"title":"أوقف الأخطاء الإملائية، وحدّد أنواع الـ overloads","prompt":"<p>أصلح ثلاث مشكلات في الـ inference في نظام الدفع في Cartwheel. لا تغيّر الاستدعاءات تحت اختبارات الأنواع.</p>\n<ol>\n<li>يجب أن ترفض <code dir=\"ltr\">applyCoupon(available, code)</code> أي <code dir=\"ltr\">code</code> ليس في <code dir=\"ltr\">available</code>، بدل أن تضيفه إلى الـ union.</li>\n<li>يجب أن ترفض <code dir=\"ltr\">createMachine({ states, initial })</code> أي حالة <code dir=\"ltr\">initial</code> ليست في <code dir=\"ltr\">states</code>.</li>\n<li>يجب أن تُعيد <code dir=\"ltr\">formatPrice</code> النوع <code dir=\"ltr\">string</code> مع رقم، و<code dir=\"ltr\">string[]</code> مع مصفوفة أرقام.</li>\n</ol>\n","hints":["في 1 و2، يجب أن يُستنتج معامل النوع من القائمة وحدها، لا من القيمة المفردة.","غلّف الموضع المخصّص للفحص فقط بـ <code dir=\"ltr\">NoInfer&lt;…&gt;</code>: <code dir=\"ltr\">code: NoInfer&lt;C&gt;</code>.","في 3، أضف توقيعَي overload فوق التنفيذ الموجود: <code dir=\"ltr\">function formatPrice(cents: number): string;</code> و<code dir=\"ltr\">function formatPrice(cents: number[]): string[];</code>."],"explanation":"<p>كان <code dir=\"ltr\">code</code> و<code dir=\"ltr\">initial</code> موقعَي استنتاج، فصار الخطأ الإملائي مرشّحًا إضافيًا واتّسع الـ union ليشمله. <code dir=\"ltr\">NoInfer&lt;C&gt;</code> تُبقي النوع كما هو لكنها تُخرج ذلك الموضع من الاستنتاج، فيأتي <code dir=\"ltr\">C</code> من القائمة وحدها وتُفحص القيمة المفردة مقابله. أما في <code dir=\"ltr\">formatPrice</code>، فتوقيع الـ union لا يستطيع أن يقول أيّ مدخل ينتج أيّ مخرج؛ وتوقيعات الـ overload تستطيع، ويبقى توقيع الـ union الأصلي كتوقيع تنفيذ مخفي.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ، بما فيها كل اختبارات الأنواع","format":"<code dir=\"ltr\">formatPrice</code> تنسّق سعرًا مفردًا وقائمة أسعار","coupon":"<code dir=\"ltr\">applyCoupon</code> ما زالت تطبّق الكوبون الصالح"}},"words":603,"readMinutes":4}