{"id":"l-at-2-2","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"توقّع ما يستنتجه TypeScript لمعامل النوع، واحتفظ بالأنواع الحرفية عبر القيود ومعاملات النوع const، ورتّب وسائط الـ callbacks كي يسري الـ inference في الاتجاه الصحيح.","html":"<p>في الدرس السابق كتبت <code dir=\"ltr\">sortByTotal(orders)</code> فاستنتج TypeScript أن <code dir=\"ltr\">T</code> هو <code dir=\"ltr\">DetailedOrder</code>. في معظم الأحيان يفعل الـ inference (استنتاج النوع) ما تريده، ولهذا تبدو المرات التي لا يفعل فيها عشوائية. لكنها ليست كذلك. يتبع TypeScript مجموعة صغيرة من القواعد، وحين تعرفها تستطيع تصميم توقيعات تستنتج بالضبط النوع الذي تقصده، دون أي <code dir=\"ltr\">&lt;…&gt;</code> في موضع الاستدعاء.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">مواقع الاستنتاج والمرشّحون</h2>\n<p>كل موضع يظهر فيه معامل النوع داخل نوع معامل هو <strong>موقع استنتاج (inference site)</strong>. عند الاستدعاء، يطابق TypeScript كل وسيط مع نوع معامله، ويجمع <strong>مرشّحًا (candidate)</strong> لمعامل النوع من كل موقع. ثم يختار نوعًا واحدًا من بين المرشّحين.</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\"> same</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">a</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\">b</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</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\"> [a, b];</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\">same</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// T = string, so string[]</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">same</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'a'</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 'string' is not assignable to parameter of type 'number'.</span></span></code></pre></div>\n<p>مع قيمتين نصيتين حرفيتين، يُدمج المرشّحان <code dir=\"ltr\">'processing'</code> و<code dir=\"ltr\">'shipped'</code> ثم يتّسعان إلى <code dir=\"ltr\">string</code>، لأن <code dir=\"ltr\">T</code> لا يُعاد إلا داخل مصفوفة (يشرح القسم التالي متى تنجو القيم الحرفية). أما مع <code dir=\"ltr\">1</code> و<code dir=\"ltr\">'a'</code>، فلا يخترع TypeScript النوع <code dir=\"ltr\">number | string</code>؛ بل يختار المرشّح الأول، <code dir=\"ltr\">number</code>، فيفشل الوسيط الثاني أمامه. وهذا مقصود. فالاتساع الصامت إلى union سيُخفي الأخطاء التي وُجدت الـ generics لالتقاطها. إن أردت قيمًا مختلطة فعلًا، فقلها صراحة: <code dir=\"ltr\">same&lt;number | string&gt;(1, 'a')</code>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-at-2-2-t1\"><defs><marker id=\"arrow-l-at-2-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-at-2-2-t1\">مسار الاستنتاج. كل وسيط من الوسيطين في موضع الاستدعاء ينتج مرشّحًا لـ T عبر موقع استنتاجه. يُدمج المرشّحون ويُوسَّعون وفق القواعد في T واحد مختار، يُعوَّض بعدها في نوع الإرجاع.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"30\" width=\"170\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"95\" y=\"57\" text-anchor=\"middle\">a: 'processing'</text>\n  <rect class=\"d-box\" x=\"10\" y=\"130\" width=\"170\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"95\" y=\"157\" text-anchor=\"middle\">b: 'shipped'</text>\n  <path class=\"d-arrow\" d=\"M180 52 L250 90\" marker-end=\"url(#arrow-l-at-2-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M180 152 L250 116\" marker-end=\"url(#arrow-l-at-2-2-1)\"/>\n  <rect class=\"d-box-warn\" x=\"254\" y=\"66\" width=\"180\" height=\"72\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"344\" y=\"94\" text-anchor=\"middle\">المرشّحون</text>\n  <text class=\"d-label-muted\" x=\"344\" y=\"118\" text-anchor=\"middle\">دمج، ثم اتساع؟</text>\n  <path class=\"d-arrow\" d=\"M434 102 L478 102\" marker-end=\"url(#arrow-l-at-2-2-1)\"/>\n  <rect class=\"d-box-primary\" x=\"482\" y=\"66\" width=\"208\" height=\"72\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"586\" y=\"94\" text-anchor=\"middle\">T المختار</text>\n  <text class=\"d-code\" x=\"586\" y=\"118\" text-anchor=\"middle\">string</text>\n  <path class=\"d-arrow\" d=\"M586 138 L586 172\" marker-end=\"url(#arrow-l-at-2-2-1)\"/>\n  <rect class=\"d-box-success\" x=\"482\" y=\"176\" width=\"208\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"586\" y=\"203\" text-anchor=\"middle\">return: T[]</text>\n</svg><figcaption>من الوسائط إلى النوع المختار إلى نوع الإرجاع</figcaption></figure>\n<h2 id=\"sec-2\" tabindex=\"-1\">متى تتّسع القيم الحرفية</h2>\n<p>القاعدة الثانية تفسّر معظم لحظات “لماذا حصلت على <code dir=\"ltr\">string</code>؟”. المرشّح الحرفي مثل <code dir=\"ltr\">'processing'</code> يُحتفظ به أو يُوسَّع حسب التوقيع:</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\"> identity</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">value</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:#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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> value; }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> box</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">value</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { value }; }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> boxStatus</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\"> 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\">value</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { value }; }</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\"> a</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> identity</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'processing'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> b</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> box</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);       </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { value: string }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> c</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> boxStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { value: 'processing' }</span></span></code></pre></div>\n<p>تنجو القيمة الحرفية حين يُعاد <code dir=\"ltr\">T</code> مباشرة في المستوى الأعلى (<code dir=\"ltr\">identity</code>)، أو حين يتضمّن قيد <code dir=\"ltr\">T</code> نوعًا أوليًا مثل <code dir=\"ltr\">string</code> (<code dir=\"ltr\">boxStatus</code>). وإلا فإنها تتّسع، بناءً على افتراض أن القيمة المخبّأة داخل كائن سيُعاد الإسناد إليها غالبًا لاحقًا.</p>\n<p>الكائنات تتبع المنطق نفسه، وهنا للقيد فائدة ثانية: يعطي الوسيطَ <strong>نوعًا سياقيًا (contextual type)</strong>.</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\"> 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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> draft</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">order</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:#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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order; }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> draftOrder</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\"> extends</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\"> OrderStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }>(</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\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order; }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">draft</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { status: string }</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">draftOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { status: 'processing' }</span></span></code></pre></div>\n<p>لأن القيد يقول إن <code dir=\"ltr\">status</code> من نوع <code dir=\"ltr\">OrderStatus</code>، يفحص TypeScript القيمة الحرفية مقابل ذلك الـ union أثناء الاستنتاج، فيثبت النوع الحرفي. القيد الجيد يوثّق الدالة ويحسّن ما تستنتجه في آنٍ واحد.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">معاملات النوع const</h2>\n<p>أحيانًا تريد وسيط المستدعي كما كُتب بالضبط: ترتيب الـ tuple، والقيم الحرفية، والكائنات المتداخلة. قبل TypeScript 5.0 كان ذلك يعني أن تطلب من كل مستدعٍ أن يكتب <code dir=\"ltr\">as const</code>. أما الآن فيمكن للدالة أن تطلب ذلك بنفسها:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> defineFlow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> readonly</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">steps</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</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\"> steps;</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\"> happyPath</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> defineFlow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'delivered'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]);</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// readonly ['processing', 'shipped', 'delivered']</span></span></code></pre></div>\n<p>المعدِّل <code dir=\"ltr\">const</code> يطلب من TypeScript أن يستنتج الوسيط كأنه مكتوب مع <code dir=\"ltr\">as const</code>: القيم الحرفية تبقى حرفية، والمصفوفات تصبح tuples للقراءة فقط، وخصائص الكائنات تصبح للقراءة فقط. اقرنه بقيد مصفوفة <code dir=\"ltr\">readonly</code>، لأن <code dir=\"ltr\">as const</code> تنتج مصفوفات للقراءة فقط. ولا يؤثّر إلا في القيم الحرفية المكتوبة مباشرة في الاستدعاء؛ أما المتغيّر الذي تمرّره فيحتفظ بأي نوع كان له أصلًا.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>متى تلجأ إلى const</span></p>\n<p>استخدم معاملات النوع <code dir=\"ltr\">const</code> للـ APIs ذات طابع التعريف: جداول المسارات، وأعلام الميزات، وآلات الحالات، ومخطّطات النماذج، وكل ما يكتب فيه المستدعي قيمة حرفية مرة واحدة وتشتقّ أنت منها الأنواع. ستستخدم واحدًا في <a href=\"/ar/lesson/advanced-typescript/l-at-2-4\">تصميم عميل API بأنواع دقيقة</a>. أما في دوال البيانات العادية مثل <code dir=\"ltr\">sortByTotal</code> فلا يضيف شيئًا.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ callbacks تُستنتج أخيرًا</h2>\n<p>الوسائط التي هي دوال بمعاملات غير معلَّقة، مثل <code dir=\"ltr\">(d) =&gt; d.totalCents</code>، <strong>حسّاسة للسياق (context-sensitive)</strong>: أنواع معاملاتها تأتي من التوقيع، الذي قد يعتمد بدوره على <code dir=\"ltr\">T</code>. يستنتج TypeScript من كل ما عداها أولًا، ثم يحدّد أنواع هذه الـ callbacks. وداخل الكائن الحرفي الواحد، يمرّ على الخصائص الحسّاسة للسياق من اليسار إلى اليمين:</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\"> createStep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">config</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">load</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:#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:#8250DF;--shiki-dark:#D2A8FF\">render</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:#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:#8250DF;--shiki-dark:#D2A8FF\">createStep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  load</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ({ orderId, totalCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }),</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  render</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> d.totalCents.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(), </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// d: { orderId: number; totalCents: number }</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\">createStep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  render</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> d.totalCents.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(), </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: 'd' is of type 'unknown'.</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  load</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ({ orderId, totalCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</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\">render</code> أولًا، لا يكون لـ <code dir=\"ltr\">T</code> أي مرشّح بعد. يمكنك الإصلاح في موضع الاستدعاء بتبديل الخاصيتين أو بتعليق <code dir=\"ltr\">orderId: number</code>، فلا تعود <code dir=\"ltr\">load</code> حسّاسة للسياق. والأفضل أن تُصلحه في الـ API نفسه: ضع المنتِج قبل المستهلِك في قوائم المعاملات وفي الأمثلة الموثّقة، كي تكون الطريقة الطبيعية لاستدعائها هي الطريقة التي تستنتج.</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\">convert&lt;string&gt;('12', Number)</code> على دالة لها معاملا نوع خطأٌ، “Expected 2 type arguments, but got 1”. لا يستنتج TypeScript البقية ما إن تحدّد أيًّا منها. فإمّا أن تتركه يستنتج كل شيء، أو تعطي المعاملات اللاحقة قيمًا افتراضية (فتأخذ عندها النوع الافتراضي لا نوعًا مستنتَجًا)، أو تقسم الدالة إلى اثنتين (<code dir=\"ltr\">convert('12').to(Number)</code>)، وهو الشكل الذي تستخدمه مكتبات كثيرة محدَّدة الأنواع لهذا السبب بالذات. الدرس القادم يغطّي القيم الافتراضية وأدوات التحكّم الأخرى في الـ inference.</p>\n</aside>\n<p>في التمرين ستجعل ثلاث دوال مساعدة في Cartwheel تحتفظ بالقيم الحرفية التي يمرّرها مستدعوها. وبعده ستتعلّم كيف تمنع الـ inference من الحدوث حيث لا ينبغي.</p>\n","toc":[{"id":"sec-1","text":"مواقع الاستنتاج والمرشّحون","level":2},{"id":"sec-2","text":"متى تتّسع القيم الحرفية","level":2},{"id":"sec-3","text":"معاملات النوع const","level":2},{"id":"sec-4","text":"الـ callbacks تُستنتج أخيرًا","level":2}],"takeaways":["كل موضع يظهر فيه معامل النوع داخل نوع معامل هو موقع استنتاج؛ يجمع TypeScript مرشّحًا من كل وسيط ثم يختار نوعًا واحدًا.","تتّسع القيم الحرفية المستنتَجة ما لم يتضمّن قيد معامل النوع نوعًا أوليًا أو يُعرَّف المعامل بـ <code dir=\"ltr\">const</code>.","معامل النوع <code dir=\"ltr\">const</code> يستنتج الوسائط كأنها مكتوبة مع <code dir=\"ltr\">as const</code>، فيحتفظ بالقيم الحرفية والـ tuples للقراءة فقط دون أن يطلب ذلك من المستدعي.","الـ callbacks ذات المعاملات غير المعلَّقة تُستنتج بعد الوسائط الأخرى، وداخل الكائن الحرفي الواحد يعمل TypeScript من اليسار إلى اليمين.","وسائط النوع الصريحة إمّا كلها أو لا شيء؛ حدّد واحدًا فيتوقّف TypeScript عن استنتاج البقية."],"further":[{"title":"Type Inference","url":"https://www.typescriptlang.org/docs/handbook/type-inference.html"},{"title":"const type parameters (TypeScript 5.0 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html"}],"quiz":[{"q":"<p>ما نوع <code dir=\"ltr\">b</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\"> box</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">value</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { value }; }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> b</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> box</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">{ value: 'processing' }</code>","why":"هذا ما ستحصل عليه مع <code dir=\"ltr\">T extends string</code> أو مع معامل نوع <code dir=\"ltr\">const</code>. أما <code dir=\"ltr\">T</code> غير المقيَّد والمتداخل في نوع الإرجاع فيتّسع."},{"text":"<code dir=\"ltr\">{ value: string }</code>","why":"صحيح. يتّسع المرشّح الحرفي لأن <code dir=\"ltr\">T</code> ليس له قيد أولي ولا يُعاد في المستوى الأعلى."},{"text":"<code dir=\"ltr\">{ value: unknown }</code>","why":"<code dir=\"ltr\">unknown</code> هو ما يلجأ إليه TypeScript حين لا يجد أي مرشّح إطلاقًا. وهنا وجد واحدًا: الوسيط النصي."}],"answer":1},{"q":"<p>أيّ تعريف يجعل <code dir=\"ltr\">defineFlow(['processing', 'shipped'])</code> تُعيد النوع <code dir=\"ltr\">readonly ['processing', 'shipped']</code> دون أن يكتب المستدعي <code dir=\"ltr\">as const</code>؟</p>","options":[{"text":"<code dir=\"ltr\">function defineFlow&lt;T&gt;(steps: T[]): T[]</code>","why":"هذا يستنتج <code dir=\"ltr\">T</code> على أنه <code dir=\"ltr\">string</code> ويُعيد <code dir=\"ltr\">string[]</code>؛ فيضيع الترتيب والقيم الحرفية معًا."},{"text":"<code dir=\"ltr\">function defineFlow&lt;T extends string&gt;(steps: T[]): T[]</code>","why":"القيد يحتفظ بالقيم الحرفية، لكنك تحصل على نوع المصفوفة <code dir=\"ltr\">('processing' | 'shipped')[]</code>، لا على tuple للقراءة فقط."},{"text":"<code dir=\"ltr\">function defineFlow&lt;const T extends readonly string[]&gt;(steps: T): T</code>","why":"صحيح. المعدِّل <code dir=\"ltr\">const</code> يستنتج الوسيط كأنه مكتوب مع <code dir=\"ltr\">as const</code>، فتحصل على tuple للقراءة فقط من القيم الحرفية."},{"text":"<code dir=\"ltr\">function defineFlow(steps: readonly string[]): readonly string[]</code>","why":"دون معامل نوع لا يوجد ما يلتقط النوع الدقيق للوسيط."}],"answer":2},{"q":"<p>لماذا نوع <code dir=\"ltr\">d</code> هنا هو <code dir=\"ltr\">unknown</code>؟</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:#8250DF;--shiki-dark:#D2A8FF\">createStep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  render</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> d.totalCents.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(),</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  load</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ({ orderId, totalCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</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","options":[{"text":"لأن <code dir=\"ltr\">render</code> تأتي قبل <code dir=\"ltr\">load</code>، ويستنتج TypeScript الدوال الحسّاسة للسياق في الكائن الحرفي من اليسار إلى اليمين.","why":"صحيح. حين تُفحص <code dir=\"ltr\">render</code>، لا يكون لـ <code dir=\"ltr\">T</code> أي مرشّح بعد. ضع <code dir=\"ltr\">load</code> أولًا، أو علّق <code dir=\"ltr\">orderId</code> بنوعه كي لا تكون <code dir=\"ltr\">load</code> حسّاسة للسياق."},{"text":"لأن <code dir=\"ltr\">T</code> لا يمكن استنتاجه من نوع إرجاع.","why":"يمكن ذلك؛ نوع إرجاع <code dir=\"ltr\">load</code> هو موقع الاستنتاج لـ <code dir=\"ltr\">T</code>. المشكلة في توقيت زيارة ذلك الموقع."},{"text":"لأن الدوال السهمية في الكائنات الحرفية تعطّل الـ inference.","why":"الـ inference يعمل جيدًا في الكائنات الحرفية؛ تبديل الخاصيتين يجعل هذا يمرّ في الترجمة."}],"answer":0},{"q":"<p>تُستدعى <code dir=\"ltr\">function convert&lt;From, To&gt;(value: From, to: (v: From) =&gt; To): To</code> هكذا: <code dir=\"ltr\">convert&lt;string&gt;('12', Number)</code>. ماذا يحدث؟</p>","options":[{"text":"يُستنتج <code dir=\"ltr\">To</code> على أنه <code dir=\"ltr\">number</code> من <code dir=\"ltr\">Number</code>.","why":"لا يقوم TypeScript بالاستنتاج الجزئي. ما إن تمرّر أي وسيط نوع صريح حتى يتوقّع كل الوسائط المطلوبة."},{"text":"خطأ، “Expected 2 type arguments, but got 1”.","why":"صحيح. وسائط النوع الصريحة إمّا كلها أو لا شيء، ما لم تكن للمعاملات المتبقّية قيم افتراضية."},{"text":"يصبح <code dir=\"ltr\">To</code> هو <code dir=\"ltr\">unknown</code>، ويمرّ الاستدعاء في الترجمة.","why":"وسائط النوع الناقصة لا تلجأ إلى قيمة بديلة بصمت إلا إذا كانت لها قيم افتراضية. ودون ذلك يُرفض الاستدعاء."}],"answer":1}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype Flag = { rollout: number; countries: readonly string[] };\n\nfunction defineFlow(steps: OrderStatus[]) {\n  return steps;\n}\n\nfunction oneOf(values: readonly string[]) {\n  return (input: string): boolean => values.includes(input);\n}\n\nfunction defineFlags(flags: Record<string, Flag>) {\n  return flags;\n}\n\n// ---- Type tests (do not edit) ----\nconst happyPath = defineFlow(['processing', 'shipped', 'delivered']);\ntype T1 = Expect<Equal<typeof happyPath, readonly ['processing', 'shipped', 'delivered']>>;\n\n// @ts-expect-error: 'refunded' is not an order status\ndefineFlow(['processing', 'refunded']);\n\nconst isChannel = oneOf(['web', 'mobile_app', 'marketplace']);\nconst raw: string = 'mobile_app';\nif (isChannel(raw)) {\n  type T2 = Expect<Equal<typeof raw, 'web' | 'mobile_app' | 'marketplace'>>;\n}\n\nconst flags = defineFlags({\n  expressShipping: { rollout: 0.5, countries: ['EG', 'AE'] },\n  giftWrap: { rollout: 1, countries: [] },\n});\ntype T3 = Expect<Equal<typeof flags.expressShipping.countries, readonly ['EG', 'AE']>>;\n\n// @ts-expect-error: every flag needs a rollout\ndefineFlags({ applePay: { countries: ['US'] } });\n\nconsole.log(happyPath, isChannel('web'), isChannel('fax'), flags.expressShipping.countries);\n"},"solution":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype Flag = { rollout: number; countries: readonly string[] };\n\nfunction defineFlow<const T extends readonly OrderStatus[]>(steps: T): T {\n  return steps;\n}\n\nfunction oneOf<const T extends readonly string[]>(values: T) {\n  return (input: string): input is T[number] => (values as readonly string[]).includes(input);\n}\n\nfunction defineFlags<const T extends Record<string, Flag>>(flags: T): T {\n  return flags;\n}\n\n// ---- Type tests (do not edit) ----\nconst happyPath = defineFlow(['processing', 'shipped', 'delivered']);\ntype T1 = Expect<Equal<typeof happyPath, readonly ['processing', 'shipped', 'delivered']>>;\n\n// @ts-expect-error: 'refunded' is not an order status\ndefineFlow(['processing', 'refunded']);\n\nconst isChannel = oneOf(['web', 'mobile_app', 'marketplace']);\nconst raw: string = 'mobile_app';\nif (isChannel(raw)) {\n  type T2 = Expect<Equal<typeof raw, 'web' | 'mobile_app' | 'marketplace'>>;\n}\n\nconst flags = defineFlags({\n  expressShipping: { rollout: 0.5, countries: ['EG', 'AE'] },\n  giftWrap: { rollout: 1, countries: [] },\n});\ntype T3 = Expect<Equal<typeof flags.expressShipping.countries, readonly ['EG', 'AE']>>;\n\n// @ts-expect-error: every flag needs a rollout\ndefineFlags({ applePay: { countries: ['US'] } });\n\nconsole.log(happyPath, isChannel('web'), isChannel('fax'), flags.expressShipping.countries);\n"},"checks":[{"id":"types","typecheck":true},{"id":"one-of","test":"const g = oneOf(['a', 'b']); return g('a') === true && g('c') === false;"},{"id":"identity","test":"const f = { x: { rollout: 1, countries: [] } }; return defineFlags(f) === f && defineFlow(['shipped']).join() === 'shipped';"}],"title":"احتفظ بالقيم الحرفية للمستدعي","prompt":"<p>ثلاث دوال تعريف مساعدة في نظام الدفع في Cartwheel تُضيّع الأنواع الدقيقة التي يكتبها مستدعوها. اجعل اختبارات الأنواع تنجح دون تغيير الاستدعاءات (بلا <code dir=\"ltr\">as const</code> في مواضع الاستدعاء):</p>\n<ul>\n<li><code dir=\"ltr\">defineFlow(steps)</code> تُعيد الخطوات كـ tuple للقراءة فقط من الحالات الحرفية، وتبقى ترفض الحالات المجهولة.</li>\n<li><code dir=\"ltr\">oneOf(values)</code> تُعيد type guard: بعد <code dir=\"ltr\">if (isChannel(raw))</code> يضيق <code dir=\"ltr\">raw</code> إلى القيم الحرفية الممرَّرة.</li>\n<li><code dir=\"ltr\">defineFlags(flags)</code> تحتفظ بالشكل الدقيق لكل علم (بما فيه الـ tuple المسمّى <code dir=\"ltr\">countries</code>) مع فحص كل علم مقابل <code dir=\"ltr\">Flag</code>.</li>\n</ul>\n<p>الدوال الثلاث تُعيد وسيطها دون تغيير وقت التشغيل (و<code dir=\"ltr\">oneOf</code> تُعيد دالة فحص).</p>\n","hints":["معامل النوع <code dir=\"ltr\">const</code> يستنتج الوسيط كأن المستدعي كتب <code dir=\"ltr\">as const</code>.","أعطِ كل دالة معامل نوع قيده هو نوع المعامل القديم، مثلًا <code dir=\"ltr\">&lt;const T extends readonly OrderStatus[]&gt;(steps: T): T</code>.","في <code dir=\"ltr\">oneOf</code>، نوع إرجاع الدالة المُعادة هو type predicate: <code dir=\"ltr\">(input: string): input is T[number] =&gt; …</code>. وفي داخلها، وسّع الـ tuple من أجل <code dir=\"ltr\">includes</code>: <code dir=\"ltr\">(values as readonly string[]).includes(input)</code>."],"explanation":"<p>دون معامل نوع، يكون نوع إرجاع كل دالة مجرّد التعليق النوعي على معاملها، فتضيع القيم الحرفية التي كتبها المستدعي. معامل النوع <code dir=\"ltr\">const</code> يلتقط الوسيط بدلالة <code dir=\"ltr\">as const</code>: الـ tuples تبقى مرتّبة وللقراءة فقط، والنصوص تبقى حرفية. والقيد (<code dir=\"ltr\">extends readonly OrderStatus[]</code> و<code dir=\"ltr\">extends Record&lt;string, Flag&gt;</code>) ما زال يتحقّق من الوسيط، فتُرفض <code dir=\"ltr\">'refunded'</code> والعلم الذي ليس فيه <code dir=\"ltr\">rollout</code>. وفي <code dir=\"ltr\">oneOf</code>، <code dir=\"ltr\">T[number]</code> هو الـ union من عناصر الـ tuple، والـ type predicate يسلّم ذلك الـ union إلى جملة <code dir=\"ltr\">if</code> لدى المستدعي.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ، بما فيها كل اختبارات الأنواع","one-of":"<code dir=\"ltr\">oneOf</code> تُعيد دالة لا تقبل إلا القيم المدرَجة","identity":"<code dir=\"ltr\">defineFlow</code> و<code dir=\"ltr\">defineFlags</code> تُعيدان وسيطهما دون تغيير"}},"words":650,"readMinutes":4}