{"id":"l-at-1-1","courseId":"advanced-typescript","lang":"ar","kind":"intro","summary":"فرّق بين type يمنع خطأً حقيقيًا وtype يكتفي بتزيين الكود، واقرأ اختبارات الأنواع التي يستخدمها كل تمرين في هذه الدورة.","html":"<p>إليك خطأً وصل إلى الإنتاج في نظام دفع عملت عليه. كانت هناك دالة استرداد تأخذ <code dir=\"ltr\">amount</code>، ومزوّد الدفع يتوقّع المبلغ بالسنتات. أحد المستدعين مرّر المبلغ بالدولارات. فاسترد العملاء 1% فقط مما يستحقّونه، ولم ينتبه أحد لأسبوعين، لأن كل سطر في ذلك الكود كان “محدَّد النوع”: <code dir=\"ltr\">amount: number</code>.</p>\n<p>الأنواع كانت موجودة، لكنها لم تستحق مكانها. <code dir=\"ltr\">number</code> وصف الكود ولم يمنع شيئًا. هذه الدورة عن النوع الآخر من الـ types (الأنواع): النوع الذي يجعل كتابة الخطأ مستحيلة.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">طريقتان لتحديد نوع الشيء نفسه</h2>\n<p>في Cartwheel، المتجر الذي ستعمل عليه طوال الدورة، خمس حالات للطلب: <code dir=\"ltr\">processing</code> و<code dir=\"ltr\">shipped</code> و<code dir=\"ltr\">delivered</code> و<code dir=\"ltr\">cancelled</code> و<code dir=\"ltr\">returned</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</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:#953800;--shiki-dark:#FFA657\">status</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> canCancel</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\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</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\"> order.status </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'procesing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// typo: always false</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>هذا يمرّ في الترجمة. <code dir=\"ltr\">status</code> من نوع <code dir=\"ltr\">string</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\">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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</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:#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>\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\"> canCancel</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\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</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\"> order.status </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'procesing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // Error: This comparison appears to be unintentional because the types</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // 'OrderStatus' and '\"procesing\"' have no overlap.</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>كود التشغيل نفسه، وسطر واحد تغيّر في الأنواع، فاختفت فئة كاملة من الأخطاء. سترى هذه الحركة مرارًا: استبدل نوعًا واسعًا (<code dir=\"ltr\">string</code> أو <code dir=\"ltr\">number</code> أو <code dir=\"ltr\">object</code>) بنوع ضيّق يقول ما هو مسموح فعلًا.</p>\n<aside class=\"callout callout-why\" 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\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">لماذا يهمّ هذا</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>القاعدة التي تتبعها هذه الدورة</span></p>\n<p>يستحق الـ type أن يُكتب حين تستطيع أن تسمّي الخطأ الذي يمنعه. إن لم تستطع فهو زينة، وللزينة ثمن: على أحدهم أن يقرأها. أفضل قواعد كود TypeScript ليست تلك التي فيها أذكى الأنواع، بل تلك التي يؤدّي فيها كل type دوره.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">المثال الذي يرافقنا</h2>\n<p>درسًا بعد درس ستبني طبقة الأنواع لعميل الـ API الخاص بالدفع في Cartwheel: الطلبات وحالاتها، ومبالغ مالية ترفض خلط العملات، والكوبونات، وعميل fetch تُحدَّد أنواع استجاباته من خريطة المسارات، والأحداث التي يطلقها الدفع، ومحلّلات تحوّل JSON غير موثوق إلى types موثوقة. يبقى كود التشغيل صغيرًا عن قصد؛ الجزء المثير هو ما يرفض الـ compiler (المترجم) قبوله.</p>\n<p>تسير الدورة في خمس خطوات. أولًا تستخدم الأنواع أداةً للتصميم: الـ structural typing، والـ narrowing، والـ discriminated unions، و<code dir=\"ltr\">satisfies</code>. ثم الـ generics مع تركيز على الـ inference. ثم البرمجة على مستوى الأنواع، مع نصيحة صادقة عن متى تتوقّف. ثم أنماط تحتاجها قواعد الكود الحقيقية: الـ branded types، وأنواع <code dir=\"ltr\">Result</code>، والتحليل عند الحدود، والأحداث المحدَّدة الأنواع. وأخيرًا الوصول إلى الإنتاج: خيارات الـ compiler، وملفات التصريحات، والـ decorators، وترحيل JavaScript.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">كيف تختبر التمارين الأنواع</h2>\n<p>معظم التمارين على مستوى الأنواع. تشغّل بيئة التجربة TypeScript 5.9 في الوضع الصارم وتعطيك أداتين مساعدتين:</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\"> T1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Equal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">OrderStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </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></code></pre></div>\n<p><code dir=\"ltr\">Equal&lt;A, B&gt;</code> يساوي <code dir=\"ltr\">true</code> فقط حين يتطابق النوعان، و<code dir=\"ltr\">Expect</code> لا يقبل إلا <code dir=\"ltr\">true</code>. إذن هذا السطر اختبار وحدة: يمرّ في الترجمة حين يكون الـ type صحيحًا ويفشل حين لا يكون.</p>\n<p>الأداة الثانية مدمجة في TypeScript. تعليق <code dir=\"ltr\">// @ts-expect-error</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:#6E7781;--shiki-dark:#8B949E\">// @ts-expect-error: 'canceled' is not a status</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">canCancel</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ id: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1001</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'canceled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });</span></span></code></pre></div>\n<p>إذا مرّ السطر الذي تحته بلا خطأ، يبلّغ TypeScript عن توجيه غير مستخدَم. هكذا يتحوّل “هذا الاستدعاء الخاطئ مرفوض” إلى اختبار، وهذا مهم بقدر “هذا الاستدعاء الصحيح مقبول”. فالـ type الذي يقبل كل شيء ينجح في كل اختبار إيجابي.</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>اللجوء إلى @ts-ignore</span></p>\n<p><code dir=\"ltr\">// @ts-ignore</code> يُخفي الخطأ ويبقى صامتًا حين يزول الخطأ، فيتعفّن مع الوقت. في الاختبارات وفي كود الإنتاج، فضّل <code dir=\"ltr\">@ts-expect-error</code> مع سبب قصير بعد نقطتين. وحين يصلح أحدهم المشكلة الأصلية، يخبره التوجيه بأنه يمكن حذفه.</p>\n</aside>\n<p>في التمرين أدناه، حالات الطلب من نوع <code dir=\"ltr\">string</code>. ضيّق الـ type وشاهد الـ compiler يجد التسمية الناقصة بدلًا منك. الدرس التالي ينظر في معنى “قابل للإسناد” (assignable) فعلًا في TypeScript، لأنه ليس ما يتوقّعه معظم القادمين من Java أو C#.</p>\n","toc":[{"id":"sec-1","text":"طريقتان لتحديد نوع الشيء نفسه","level":2},{"id":"sec-2","text":"المثال الذي يرافقنا","level":2},{"id":"sec-3","text":"كيف تختبر التمارين الأنواع","level":2}],"takeaways":["يستحق الـ type مكانه حين يجعل كتابة خطأ حقيقي مستحيلة، لا حين يكرّر ما يقوله الكود أصلًا.","استخدام <code dir=\"ltr\">string</code> حيث لا تصحّ إلا خمس قيم فرصةٌ ضائعة؛ أما الـ union من القيم الحرفية فيسمح للـ compiler بفحص كل استخدام.","<code dir=\"ltr\">Expect&lt;Equal&lt;A, B&gt;&gt;</code> يفشل في الترجمة ما لم يكن النوعان متطابقين تمامًا، لذا يعمل كاختبار وحدة للأنواع.","سطر <code dir=\"ltr\">// @ts-expect-error</code> يؤكّد أن السطر التالي مرفوض، ويصبح هو نفسه خطأً إذا مرّ ذلك السطر بلا خطأ."],"further":[{"title":"TypeScript for JavaScript Programmers","url":"https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html"},{"title":"Everyday Types","url":"https://www.typescriptlang.org/docs/handbook/2/everyday-types.html"}],"quiz":[{"q":"<p>أيّ هذه الأنواع يلتقط أكبر عدد من الأخطاء الحقيقية لحقل <code dir=\"ltr\">status</code> في طلب لا يمكن أن يكون إلا <code dir=\"ltr\">processing</code> أو <code dir=\"ltr\">shipped</code> أو <code dir=\"ltr\">delivered</code> أو <code dir=\"ltr\">cancelled</code> أو <code dir=\"ltr\">returned</code>؟</p>","options":[{"text":"<code dir=\"ltr\">string</code>، مع تعليق يسرد القيم الخمس","why":"لا أحد يفحص التعليقات. القيمة <code dir=\"ltr\">'canceled'</code> بحرف L واحد تمرّ بلا مشكلة وتفشل بصمت وقت التشغيل."},{"text":"<code dir=\"ltr\">'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned'</code>","why":"صحيح. كل إسناد ومقارنة يُفحص مقابل القيم الخمس، ويستطيع الـ compiler أن يعرف متى تُغفل جملة switch إحداها."},{"text":"<code dir=\"ltr\">any</code>، لأن القيمة تأتي من قاعدة البيانات على أي حال","why":"<code dir=\"ltr\">any</code> يُطفئ الفحص في كل استخدام للقيمة، فيمرّ أي خطأ إملائي في أي مكان لاحق دون أن يلاحظه أحد."},{"text":"<code dir=\"ltr\">String</code>، نوع الكائن المغلِّف، لأنه أكثر صرامة من <code dir=\"ltr\">string</code>","why":"<code dir=\"ltr\">String</code> هو نوع الكائن المغلِّف وليس أكثر صرامة؛ يقبل أي نص، ونادرًا جدًا ما يكون ما تريده."}],"answer":1},{"q":"<p>ماذا يفعل اختبار النوع هذا حين يكون <code dir=\"ltr\">Total</code> هو <code dir=\"ltr\">number | undefined</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Equal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Total</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>>;</span></span></code></pre></div>\n","options":[{"text":"يمرّ في الترجمة، لأن <code dir=\"ltr\">number</code> جزء من الـ union.","why":"<code dir=\"ltr\">Equal</code> يتحقّق من أن النوعين متطابقان، لا من أن أحدهما يتداخل مع الآخر. الـ union الذي يضم <code dir=\"ltr\">undefined</code> نوعٌ مختلف."},{"text":"يمرّ في الترجمة، لكنه يطبع تحذيرًا وقت التشغيل.","why":"تُمحى الأنواع قبل تشغيل الكود، لذا لا يفشل اختبار النوع إلا وقت الترجمة؛ لا يبقى شيء ليُطبع."},{"text":"يفشل في الترجمة، لأن <code dir=\"ltr\">Equal</code> يُحَلّ إلى <code dir=\"ltr\">false</code> و<code dir=\"ltr\">Expect</code> لا يقبل إلا <code dir=\"ltr\">true</code>.","why":"صحيح. <code dir=\"ltr\">Expect&lt;T extends true&gt;</code> يرفض <code dir=\"ltr\">false</code>، فيظهر عدم التطابق كخطأ نوع على ذلك السطر."}],"answer":2},{"q":"<p>تضيف <code dir=\"ltr\">// @ts-expect-error</code> فوق <code dir=\"ltr\">pay(order, 'EUR')</code>، لكن <code dir=\"ltr\">pay</code> تقبل أي نص كعملة. ماذا يحدث؟</p>","options":[{"text":"لا شيء؛ يُتجاهَل التوجيه حين لا يوجد خطأ ليُخفيه.","why":"هذا سلوك <code dir=\"ltr\">// @ts-ignore</code>. أما <code dir=\"ltr\">@ts-expect-error</code> فأكثر صرامة ويشتكي حين لا يجد ما يُخفيه."},{"text":"يبلّغ TypeScript عن “Unused ‘@ts-expect-error’ directive”، فيفشل الاختبار.","why":"صحيح. التوجيه <code dir=\"ltr\">@ts-expect-error</code> غير المستخدَم هو خطأ بحد ذاته، ولهذا بالضبط يصلح اختبارًا على أن الاستدعاء الخاطئ مرفوض."},{"text":"يُحذف الاستدعاء من JavaScript الناتجة.","why":"التعليقات لا تغيّر الكود الناتج أبدًا. يبقى الاستدعاء ويُنفَّذ؛ لا يتأثّر إلا فحص الأنواع."}],"answer":1}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"// Cartwheel's five order statuses come from the orders table.\n// Right now the type accepts any string at all.\ntype OrderStatus = string;\n\nconst statusLabels: Record<OrderStatus, string> = {\n  processing: 'Processing',\n  shipped: 'Shipped',\n  delivered: 'Delivered',\n  cancelled: 'Cancelled',\n};\n\nfunction statusLabel(status: OrderStatus): string {\n  return statusLabels[status];\n}\n\nfunction canCancel(status: OrderStatus): boolean {\n  return status === 'processing';\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<OrderStatus, 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned'>>;\n\n// @ts-expect-error: 'canceled' (one L) is a typo, not a status\ncanCancel('canceled');\n\nconsole.log(statusLabel('shipped'), canCancel('processing'));\n"},"solution":{"ts":"// Cartwheel's five order statuses come from the orders table.\ntype OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\n\nconst statusLabels: Record<OrderStatus, string> = {\n  processing: 'Processing',\n  shipped: 'Shipped',\n  delivered: 'Delivered',\n  cancelled: 'Cancelled',\n  returned: 'Returned',\n};\n\nfunction statusLabel(status: OrderStatus): string {\n  return statusLabels[status];\n}\n\nfunction canCancel(status: OrderStatus): boolean {\n  return status === 'processing';\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<OrderStatus, 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned'>>;\n\n// @ts-expect-error: 'canceled' (one L) is a typo, not a status\ncanCancel('canceled');\n\nconsole.log(statusLabel('shipped'), canCancel('processing'));\n"},"checks":[{"id":"types","typecheck":true},{"id":"returned-label","test":"return statusLabel('returned') === 'Returned';"},{"id":"can-cancel","test":"return canCancel('processing') === true && canCancel('shipped') === false;"}],"title":"ضيّق نوع حالة الطلب","prompt":"<p>لطلبات Cartwheel خمس حالات بالضبط: <code dir=\"ltr\">processing</code> و<code dir=\"ltr\">shipped</code> و<code dir=\"ltr\">delivered</code> و<code dir=\"ltr\">cancelled</code> و<code dir=\"ltr\">returned</code>. استبدل <code dir=\"ltr\">string</code> في <code dir=\"ltr\">OrderStatus</code> بـ union من هذه القيم الحرفية الخمس حتى تنجح اختبارات الأنواع. وحين يضيق النوع، يشير الـ compiler إلى فجوة في <code dir=\"ltr\">statusLabels</code>: املأها حتى تُعيد <code dir=\"ltr\">statusLabel('returned')</code> القيمة <code dir=\"ltr\">'Returned'</code>.</p>\n","hints":["الـ union من القيم النصية الحرفية يبدو هكذا: <code dir=\"ltr\">'a' | 'b' | 'c'</code>.","بعد تغيير <code dir=\"ltr\">OrderStatus</code>، اقرأ الخطأ الجديد على <code dir=\"ltr\">statusLabels</code>: صار <code dir=\"ltr\">Record&lt;OrderStatus, string&gt;</code> يتطلّب مفتاحًا لكل حالة.","أضف <code dir=\"ltr\">returned: 'Returned'</code> إلى الكائن."],"explanation":"<p>حين يكون <code dir=\"ltr\">OrderStatus</code> من نوع <code dir=\"ltr\">string</code>، يعني <code dir=\"ltr\">Record&lt;OrderStatus, string&gt;</code> “أي مفاتيح نصية”، فتكون الحالة الناقصة غير مرئية، والخطأ الإملائي <code dir=\"ltr\">'canceled'</code> وسيطًا صالحًا. أما حين يصير union من خمس قيم حرفية، فيجب أن يحتوي الـ record هذه المفاتيح الخمسة بالضبط، وتُرفض <code dir=\"ltr\">canCancel('canceled')</code>، وهذا ما يؤكّده سطر <code dir=\"ltr\">@ts-expect-error</code>. لم يتغيّر شيء وقت التشغيل سوى التسمية المضافة: الأنواع وحدها وجدت المشكلتين.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ (بما فيها اختبارات الأنواع)","returned-label":"<code dir=\"ltr\">statusLabel('returned')</code> تُعيد <code dir=\"ltr\">'Returned'</code>","can-cancel":"<code dir=\"ltr\">canCancel</code> ما زالت لا تسمح بإلغاء إلا الطلبات في حالة <code dir=\"ltr\">processing</code>"}},"words":522,"readMinutes":3}