{"id":"l-at-1-4","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"استبدل الأنواع المكدَّسة بالحقول الاختيارية بالـ discriminated unions، كي تحمل كل حالة طلب البيانات التي تملكها بالضبط، وتتوقّف الحالات المستحيلة عن المرور في الترجمة.","html":"<p>هكذا كان شكل نوع <code dir=\"ltr\">Order</code> في 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\">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>\n<span class=\"line\"><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>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  status</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:#953800;--shiki-dark:#FFA657\">  trackingNumber</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:#6E7781;--shiki-dark:#8B949E\">// set once shipped</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  deliveredAt</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:#6E7781;--shiki-dark:#8B949E\">// set once delivered</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  cancelReason</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:#6E7781;--shiki-dark:#8B949E\">// set if cancelled</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  refundCents</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:#6E7781;--shiki-dark:#8B949E\">// set if returned</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span></code></pre></div>\n<p>يبدو معقولًا، لكنه فخ. كل واحد من الحقول الاختيارية الأربعة يمكن أن يحضر أو يغيب، فيسمح هذا النوع بـ 5 × 2⁴ = 80 شكلًا. وعمل Cartwheel فيه خمسة فقط. طلب مشحون بلا رقم تتبّع يمرّ في الترجمة. طلب ملغى فيه تاريخ تسليم يمرّ. وفي كل مرة تقرأ <code dir=\"ltr\">order.trackingNumber</code> تحصل على <code dir=\"ltr\">string | undefined</code> فتمدّ يدك إلى <code dir=\"ltr\">!</code>، أي أنك تقول للـ compiler “ثق بي” ثمانين مرة في الأسبوع.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">نوع واحد لكل حالة</h2>\n<p>الـ <strong>discriminated union (الاتحاد المميَّز)</strong> يعطي كل حالة نوع كائن خاصًا بها ويجمعها بـ <code dir=\"ltr\">|</code>. لكل عضو خاصية مشتركة بقيمة حرفية مختلفة، هي <strong>المميِّز (discriminant)</strong>:</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\"> ProcessingOrder</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:#0A3069;--shiki-dark:#A5D6FF\"> 'processing'</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\"> ShippedOrder</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:#0A3069;--shiki-dark:#A5D6FF\"> 'shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">trackingNumber</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> DeliveredOrder</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:#0A3069;--shiki-dark:#A5D6FF\"> 'delivered'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">trackingNumber</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\">deliveredAt</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CancelledOrder</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:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">cancelReason</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ReturnedOrder</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#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>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  status</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:#953800;--shiki-dark:#FFA657\">  trackingNumber</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:#953800;--shiki-dark:#FFA657\">  deliveredAt</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:#953800;--shiki-dark:#FFA657\">  refundCents</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:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ProcessingOrder</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippedOrder</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> DeliveredOrder</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CancelledOrder</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ReturnedOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p>الآن يسمح النوع بخمسة أشكال بالضبط. لاحظ أنه لم يعد شيء اختياريًا: الطلب المشحون <em>له</em> رقم تتبّع. الحالات المستحيلة لا تُفحص وقت التشغيل؛ بل لا يمكن التعبير عنها أصلًا:</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> o</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</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\"> { id: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1042</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Type '{ id: number; status: \"shipped\"; }' is not assignable to type 'Order'.</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   Property 'trackingNumber' is missing in type '{ id: number; status: \"shipped\"; }'</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   but required in type 'ShippedOrder'.</span></span></code></pre></div>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 220\" role=\"img\" aria-labelledby=\"l-at-1-4-t1\"><defs><marker id=\"arrow-l-at-1-4-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-1-4-t1\">آلة حالات الطلب. processing ينتقل إلى shipped عبر ship، وshipped إلى delivered عبر deliver، وdelivered إلى returned عبر refund. ويمكن أن ينتقل processing أيضًا إلى cancelled عبر cancel. كل صندوق حالة يسرد الحقول التي يضيفها.</title>\n  <rect class=\"d-box-primary\" x=\"10\" y=\"40\" width=\"150\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"85\" y=\"68\" text-anchor=\"middle\">processing</text>\n  <text class=\"d-label-muted\" x=\"85\" y=\"90\" text-anchor=\"middle\">id</text>\n  <rect class=\"d-box-accent\" x=\"190\" y=\"40\" width=\"150\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"265\" y=\"68\" text-anchor=\"middle\">shipped</text>\n  <text class=\"d-label-muted\" x=\"265\" y=\"90\" text-anchor=\"middle\">+ trackingNumber</text>\n  <rect class=\"d-box-success\" x=\"370\" y=\"40\" width=\"150\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"445\" y=\"68\" text-anchor=\"middle\">delivered</text>\n  <text class=\"d-label-muted\" x=\"445\" y=\"90\" text-anchor=\"middle\">+ deliveredAt</text>\n  <rect class=\"d-box\" x=\"550\" y=\"40\" width=\"140\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"620\" y=\"68\" text-anchor=\"middle\">returned</text>\n  <text class=\"d-label-muted\" x=\"620\" y=\"90\" text-anchor=\"middle\">+ refundCents</text>\n  <rect class=\"d-box-warn\" x=\"10\" y=\"150\" width=\"150\" height=\"64\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"85\" y=\"178\" text-anchor=\"middle\">cancelled</text>\n  <text class=\"d-label-muted\" x=\"85\" y=\"200\" text-anchor=\"middle\">+ cancelReason</text>\n  <path class=\"d-arrow\" d=\"M160 72 L186 72\" marker-end=\"url(#arrow-l-at-1-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M340 72 L366 72\" marker-end=\"url(#arrow-l-at-1-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M520 72 L546 72\" marker-end=\"url(#arrow-l-at-1-4-1)\"/>\n  <path class=\"d-arrow\" d=\"M85 104 L85 146\" marker-end=\"url(#arrow-l-at-1-4-1)\"/>\n  <text class=\"d-code\" x=\"173\" y=\"30\" text-anchor=\"middle\">ship</text>\n  <text class=\"d-code\" x=\"353\" y=\"30\" text-anchor=\"middle\">deliver</text>\n  <text class=\"d-code\" x=\"533\" y=\"30\" text-anchor=\"middle\">refund</text>\n  <text class=\"d-code\" x=\"100\" y=\"130\">cancel</text>\n</svg><figcaption>كل حالة نوعٌ مستقل؛ والانتقالات دوال بينها</figcaption></figure>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ narrowing على المميِّز</h2>\n<p>فحص المميِّز يضيّق الكائن كله. داخل كل <code dir=\"ltr\">case</code>، يعرف TypeScript بالضبط أيّ عضو بين يديك:</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\"> describe</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\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  switch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (order.status) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'processing'</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:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">id</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} is being packed`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'shipped'</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:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">id</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} is on its way (${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">trackingNumber</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:#CF222E;--shiki-dark:#FF7B72\">    case</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:#CF222E;--shiki-dark:#FF7B72\">      return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `#${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">id</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} arrived on ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">deliveredAt</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:#CF222E;--shiki-dark:#FF7B72\">    case</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</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:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">id</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} was cancelled: ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cancelReason</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:#CF222E;--shiki-dark:#FF7B72\">    case</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\">      return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `#${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">id</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} was refunded $${</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">order</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">refundCents</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 style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>لا <code dir=\"ltr\">!</code> ولا <code dir=\"ltr\">?.</code> ولا <code dir=\"ltr\">?? ''</code>. ولاحظ نوع الإرجاع الصريح <code dir=\"ltr\">: string</code>. إذا أضاف أحدهم الحالة <code dir=\"ltr\">'on-hold'</code> إلى الـ union الشهر القادم، تصبح نهاية هذه الدالة قابلة للوصول، فيبلّغ TypeScript عن <em>Function lacks ending return statement and return type does not include ‘undefined’</em>. الـ compiler يجد كل مكان يحتاج إلى تحديث. وستجعل هذا الفحص صريحًا وأعلى صوتًا باستخدام <code dir=\"ltr\">never</code> في <a href=\"/ar/lesson/advanced-typescript/l-at-4-1\">الـ Branded Types والفحوص الشاملة</a>.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">الانتقالات دوالٌ بين الحالات</h2>\n<p>يؤتي الـ union أكبر ثماره في الدوال التي تغيّر الحالة. حدّد أنواع مدخلاتها ومخرجاتها كأشكال محدّدة:</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\"> ship</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\"> ProcessingOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">trackingNumber</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippedOrder</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\"> { id: order.id, status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, trackingNumber };</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\"> cancel</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\"> ProcessingOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">reason</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CancelledOrder</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\"> { id: order.id, status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cancelled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, cancelReason: reason };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الآن صار “لا يمكنك شحن طلب مرتين” و&quot;لا يمكنك إلغاء طلب مشحون&quot; حقيقتين وقت الترجمة. المستدعي الذي بين يديه <code dir=\"ltr\">Order</code> عليه أن يضيّق النوع أولًا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> shipIfReady</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:#953800;--shiki-dark:#FFA657\">tracking</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#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\">  if</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\"> 'processing'</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\">  return</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> ship</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(order, tracking); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// order is ProcessingOrder here</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>أنواع الإرجاع مهمة بقدر المعاملات. لأن <code dir=\"ltr\">ship</code> تُعيد <code dir=\"ltr\">ShippedOrder</code> لا <code dir=\"ltr\">Order</code>، يستطيع المستدعي تسليم النتيجة مباشرة إلى دالة تحتاج رقم تتبّع، مثل <code dir=\"ltr\">printLabel(shipped)</code>، دون فحص <code dir=\"ltr\">status</code> مرة أخرى. كل انتقال يحمل المعرفة إلى الأمام. أعِد الـ <code dir=\"ltr\">Order</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>لا يعمل النمط إلا حين يكون مميِّز كل عضو literal type (نوعًا حرفيًا). إذا وصل <code dir=\"ltr\">status</code> بنوع <code dir=\"ltr\">string</code> (من استجابة API بلا أنواع مثلًا)، فلن يضيّق فحصه شيئًا، وتعود إلى <code dir=\"ltr\">!</code> في كل مكان. حلّل الاستجابة إلى الـ union عند حافة تطبيقك، وهذا موضوع <a href=\"/ar/lesson/advanced-typescript/l-at-4-3\">تحليل البيانات غير الموثوقة عند الحدود</a>، وأبقِ <code dir=\"ltr\">string</code> خارج القلب.</p>\n</aside>\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>سمِّ الأشكال</span></p>\n<p>يمكنك كتابة الـ union مباشرة دون أسماء، لكن تسمية كل عضو (<code dir=\"ltr\">ShippedOrder</code> و<code dir=\"ltr\">CancelledOrder</code>) تمنحك المفردات اللازمة لدوال الانتقال وتجعل رسائل الخطأ مقروءة. رسالة عن <code dir=\"ltr\">ShippedOrder</code> أسهل بكثير في التصرّف بناءً عليها من رسالة عن نوع كائن مجهول من 200 حرف.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">مشاركة الحقول المشتركة</h2>\n<p>للطلبات الحقيقية أكثر من <code dir=\"ltr\">id</code>: عميل، وطابع زمني لوقت الطلب، وبنود. تكرار هذه في خمسة أشكال يدعو إلى التباعد بينها، فاسحبها إلى نوع أساسي وقاطعه مع حقول كل حالة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"6\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderBase</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\">customerId</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\">placedAt</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Shipped</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderBase</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;</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:#0A3069;--shiki-dark:#A5D6FF\"> 'shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">trackingNumber</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Cancelled</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderBase</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;</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:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">cancelReason</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></code></pre></div>\n<p>يبقى كل شكل عضوًا مستقلًا بـ <code dir=\"ltr\">status</code> حرفي خاص به، فيعمل الـ narrowing تمامًا كما من قبل. ويعطيك النوع الأساسي أيضًا نوع معامل طبيعيًا للدوال التي لا تهتم بالحالة، مثل <code dir=\"ltr\">function customerLink(order: OrderBase)</code>، التي تقبل كل الأشكال.</p>\n<p>يمكنك أيضًا تفكيك المميِّز. منذ TypeScript 4.4، تضيّق <code dir=\"ltr\">const { status } = order; if (status === 'shipped') { … }</code> النوعَ <code dir=\"ltr\">order</code> أيضًا، ما دام <code dir=\"ltr\">order</code> ثابتًا <code dir=\"ltr\">const</code> أو معاملًا لم يُعَد الإسناد إليه. هذا يُبقي جمل <code dir=\"ltr\">switch</code> الطويلة مقروءة دون التخلّي عن الـ narrowing.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">متى لا تستخدمه</h2>\n<p>الـ discriminated union هو الخيار الصحيح حين تحمل الحالات <em>بيانات مختلفة</em>. أما حين تختلف الأشكال في التسمية فقط (مثلًا <code dir=\"ltr\">channel</code> بقيم <code dir=\"ltr\">'web' | 'mobile_app' | 'marketplace'</code> والحقول نفسها)، فيكفي union حرفي بسيط على خاصية واحدة. الجأ إلى النمط الكامل حين تضبط نفسك تكتب حقولًا اختيارية مع تعليقات مثل “تُضبط فقط حين…”.</p>\n<p>في التمرين، ستحوّل <code dir=\"ltr\">Order</code> المليء بالحقول الاختيارية في Cartwheel إلى union، وتجعل <code dir=\"ltr\">ship</code> لا تقبل إلا الطلبات القابلة للشحن. الدرس القادم: الـ literal types و<code dir=\"ltr\">as const</code> و<code dir=\"ltr\">satisfies</code>، الأدوات التي تمنع تلك القيم الحرفية من الاتساع مجددًا إلى <code dir=\"ltr\">string</code>.</p>\n","toc":[{"id":"sec-1","text":"نوع واحد لكل حالة","level":2},{"id":"sec-2","text":"الـ narrowing على المميِّز","level":2},{"id":"sec-3","text":"الانتقالات دوالٌ بين الحالات","level":2},{"id":"sec-4","text":"مشاركة الحقول المشتركة","level":2},{"id":"sec-5","text":"متى لا تستخدمه","level":2}],"takeaways":["النوع المكوَّن من حقول اختيارية يسمح بكل تركيباتها، ومعظمها حالات لا يمكن أن تقع في عملك أبدًا.","الـ discriminated union يعطي كل حالة نوع كائن خاصًا بها، وتربط بينها خاصية حرفية مشتركة مثل <code dir=\"ltr\">status</code>.","فحص المميِّز بـ <code dir=\"ltr\">===</code> أو <code dir=\"ltr\">switch</code> يضيّق الكائن كله، فتصبح الحقول الخاصة بكل حالة متاحة دون <code dir=\"ltr\">!</code>.","الدوال التي تنقل بين الحالات يجب أن تقبل وتُعيد أشكالًا محدّدة، مثل <code dir=\"ltr\">ship(order: ProcessingOrder): ShippedOrder</code>.","نوع الإرجاع الصريح على جملة <code dir=\"ltr\">switch</code> فوق المميِّز يحوّل الحالة المنسيّة إلى خطأ ترجمة."],"further":[{"title":"Discriminated unions (Narrowing)","url":"https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions"},{"title":"Unions and intersection types (Everyday Types)","url":"https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types"}],"quiz":[{"q":"<p>نوع <code dir=\"ltr\">Order</code> فيه <code dir=\"ltr\">status</code> بخمس قيم وأربعة حقول اختيارية. كم شكلًا تقريبًا يسمح به النوع، وكم منها حالات صالحة إذا كانت كل حالة تستخدم مجموعة ثابتة من الحقول؟</p>","options":[{"text":"5 مسموحة و5 صالحة، لأن <code dir=\"ltr\">status</code> يتحكّم في الحقول الموجودة.","why":"لا شيء في نوع الحقول الاختيارية يربط الحقول بـ <code dir=\"ltr\">status</code>. يراها الـ compiler مستقلة."},{"text":"80 مسموحة (5 × 2⁴) و5 صالحة.","why":"صحيح. كل حقل اختياري يمكن أن يحضر أو يغيب باستقلال، فمعظم التركيبات حالات مستحيلة ومع ذلك تمرّ في الترجمة."},{"text":"20 مسموحة (5 × 4) و5 صالحة.","why":"الحقول الاختيارية تتضاعف ولا تُجمع. أربعة خيارات مستقلة بنعم/لا تعطي 16 تركيبة لكل حالة."}],"answer":1},{"q":"<p>بالنظر إلى الـ union أدناه، ما نوع <code dir=\"ltr\">order</code> داخل فرع <code dir=\"ltr\">case '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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  |</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:#0A3069;--shiki-dark:#A5D6FF\"> 'processing'</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  |</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:#0A3069;--shiki-dark:#A5D6FF\"> 'delivered'</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\">deliveredAt</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\">  |</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:#0A3069;--shiki-dark:#A5D6FF\"> 'returned'</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\">deliveredAt</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\">refundCents</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></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">Order</code>، لأن <code dir=\"ltr\">switch</code> لا تضيّق الكائنات","why":"الـ switch على مميِّز حرفي يضيّق الكائن كله، وهذا هو جوهر النمط."},{"text":"<code dir=\"ltr\">{ status: 'delivered' … } | { status: 'returned' … }</code>، لأن كليهما فيه <code dir=\"ltr\">deliveredAt</code>","why":"الـ narrowing يتبع قيمة المميِّز، لا الحقول المشتركة. عضو واحد فقط فيه <code dir=\"ltr\">status: 'returned'</code>."},{"text":"<code dir=\"ltr\">{ status: 'returned'; id: number; deliveredAt: string; refundCents: number }</code>","why":"صحيح. عضو واحد فقط يمكن أن يساوي <code dir=\"ltr\">status</code> فيه <code dir=\"ltr\">'returned'</code>، فيكون <code dir=\"ltr\">refundCents</code> متاحًا كـ <code dir=\"ltr\">number</code> عادي."}],"answer":2},{"q":"<p>أيّ توقيع يستخدم الـ union على أفضل وجه لمنع شحن الطلب مرتين؟</p>","options":[{"text":"<code dir=\"ltr\">ship(order: Order, tracking: string): Order</code>","why":"هذا يقبل أيضًا طلبًا مُسلَّمًا أو ملغى، ويفقد المستدعي معرفة أن النتيجة مشحونة."},{"text":"<code dir=\"ltr\">ship(order: Order, tracking: string): ShippedOrder</code>","why":"نوع الإرجاع أفضل، لكن ما زال يمكن تمرير أي طلب، بما فيه طلب شُحن من قبل."},{"text":"<code dir=\"ltr\">ship(order: ShippedOrder, tracking: string): ShippedOrder</code>","why":"هذا لا يقبل إلا الطلبات المشحونة أصلًا، وهو عكس ما يجب أن تسمح به الدالة."},{"text":"<code dir=\"ltr\">ship(order: ProcessingOrder, tracking: string): ShippedOrder</code>","why":"صحيح. لا يدخل إلا طلب قيد المعالجة، ويحصل المستدعي على قيمة يعرف الـ compiler أنها مشحونة."}],"answer":3},{"q":"<p>تضيف الحالة <code dir=\"ltr\">'on-hold'</code> إلى الـ union المسمّى <code dir=\"ltr\">Order</code>. أيّ دالة مضمونٌ أن تحصل على خطأ ترجمة إلى أن تتعامل مع الحالة الجديدة؟</p>","options":[{"text":"دالة نوع إرجاعها <code dir=\"ltr\">string</code> وفيها <code dir=\"ltr\">switch</code> يعود في كل حالة وليس فيه <code dir=\"ltr\">default</code>.","why":"صحيح. مع وجود عضو جديد غير مُعالَج، تصبح نهاية الدالة قابلة للوصول، فيبلّغ TypeScript أنها تفتقد جملة عودة ختامية."},{"text":"دالة تستخدم <code dir=\"ltr\">if (order.status === 'shipped') … else …</code>.","why":"فرع <code dir=\"ltr\">else</code> يمتصّ الحالة الجديدة بصمت، وهذا بالضبط الخطأ الذي تريد تجنّبه."},{"text":"دالة فيها فرع <code dir=\"ltr\">default:</code> يُعيد <code dir=\"ltr\">'Unknown'</code>.","why":"فرع <code dir=\"ltr\">default</code> يعالج الحالة الجديدة وقت التشغيل، فلا يجد الـ compiler سببًا للشكوى."}],"answer":0}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"// Every field that only some statuses have is optional,\n// so impossible states (shipped with no tracking number…) compile.\ntype Order = {\n  id: number;\n  status: 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\n  trackingNumber?: string; // shipped, delivered, returned\n  deliveredAt?: string;    // delivered, returned\n  cancelReason?: string;   // cancelled\n  refundCents?: number;    // returned\n};\n\nfunction ship(order: Order, trackingNumber: string): Order {\n  return { id: order.id, status: 'shipped', trackingNumber };\n}\n\nfunction describe(order: Order): string {\n  switch (order.status) {\n    case 'processing': return `#${order.id} is being packed`;\n    case 'shipped': return `#${order.id} is on its way (${order.trackingNumber})`;\n    case 'delivered': return `#${order.id} arrived on ${order.deliveredAt}`;\n    case 'cancelled': return `#${order.id} was cancelled: ${order.cancelReason}`;\n    case 'returned': return `#${order.id} was refunded $${(order.refundCents! / 100).toFixed(2)}`;\n  }\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<Order['status'], 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned'>>;\n\n// @ts-expect-error: a shipped order must have a tracking number\nconst noTracking: Order = { id: 1, status: 'shipped' };\n\n// @ts-expect-error: a cancelled order cannot have a delivery date\nconst cancelledButDelivered: Order = { id: 2, status: 'cancelled', cancelReason: 'Out of stock', deliveredAt: '2025-05-02' };\n\n// @ts-expect-error: only processing orders can be shipped\nship({ id: 3, status: 'delivered', trackingNumber: '1Z-77', deliveredAt: '2025-05-02' }, '1Z-88');\n\nconsole.log(describe(ship({ id: 1042, status: 'processing' }, '1Z-999')));\n"},"solution":{"ts":"type ProcessingOrder = { id: number; status: 'processing' };\ntype ShippedOrder = { id: number; status: 'shipped'; trackingNumber: string };\ntype DeliveredOrder = { id: number; status: 'delivered'; trackingNumber: string; deliveredAt: string };\ntype CancelledOrder = { id: number; status: 'cancelled'; cancelReason: string };\ntype ReturnedOrder = {\n  id: number;\n  status: 'returned';\n  trackingNumber: string;\n  deliveredAt: string;\n  refundCents: number;\n};\n\ntype Order = ProcessingOrder | ShippedOrder | DeliveredOrder | CancelledOrder | ReturnedOrder;\n\nfunction ship(order: ProcessingOrder, trackingNumber: string): ShippedOrder {\n  return { id: order.id, status: 'shipped', trackingNumber };\n}\n\nfunction describe(order: Order): string {\n  switch (order.status) {\n    case 'processing': return `#${order.id} is being packed`;\n    case 'shipped': return `#${order.id} is on its way (${order.trackingNumber})`;\n    case 'delivered': return `#${order.id} arrived on ${order.deliveredAt}`;\n    case 'cancelled': return `#${order.id} was cancelled: ${order.cancelReason}`;\n    case 'returned': return `#${order.id} was refunded $${(order.refundCents / 100).toFixed(2)}`;\n  }\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<Order['status'], 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned'>>;\n\n// @ts-expect-error: a shipped order must have a tracking number\nconst noTracking: Order = { id: 1, status: 'shipped' };\n\n// @ts-expect-error: a cancelled order cannot have a delivery date\nconst cancelledButDelivered: Order = { id: 2, status: 'cancelled', cancelReason: 'Out of stock', deliveredAt: '2025-05-02' };\n\n// @ts-expect-error: only processing orders can be shipped\nship({ id: 3, status: 'delivered', trackingNumber: '1Z-77', deliveredAt: '2025-05-02' }, '1Z-88');\n\nconsole.log(describe(ship({ id: 1042, status: 'processing' }, '1Z-999')));\n"},"checks":[{"id":"types","typecheck":true},{"id":"ships","test":"const s = ship({ id: 7, status: 'processing' }, 'TRK-1'); return s.status === 'shipped' && s.trackingNumber === 'TRK-1' && s.id === 7;"},{"id":"describes","test":"return describe({ id: 9, status: 'returned', trackingNumber: 'T', deliveredAt: '2025-05-02', refundCents: 1250 }).includes('12.50') && describe({ id: 9, status: 'cancelled', cancelReason: 'Out of stock' }).includes('Out of stock');"}],"title":"حوّل الطلب إلى discriminated union","prompt":"<p>استبدل <code dir=\"ltr\">Order</code> المليء بالحقول الاختيارية بـ discriminated union من خمسة أشكال (واحد لكل <code dir=\"ltr\">status</code>)، يكون فيه كل حقل تملكه الحالة إلزاميًا، والحقول التي لا تملكها غائبة:</p>\n<ul>\n<li><code dir=\"ltr\">processing</code>: <code dir=\"ltr\">id</code></li>\n<li><code dir=\"ltr\">shipped</code>: <code dir=\"ltr\">id</code>، <code dir=\"ltr\">trackingNumber</code></li>\n<li><code dir=\"ltr\">delivered</code>: <code dir=\"ltr\">id</code>، <code dir=\"ltr\">trackingNumber</code>، <code dir=\"ltr\">deliveredAt</code></li>\n<li><code dir=\"ltr\">cancelled</code>: <code dir=\"ltr\">id</code>، <code dir=\"ltr\">cancelReason</code></li>\n<li><code dir=\"ltr\">returned</code>: <code dir=\"ltr\">id</code>، <code dir=\"ltr\">trackingNumber</code>، <code dir=\"ltr\">deliveredAt</code>، <code dir=\"ltr\">refundCents</code></li>\n</ul>\n<p>ثم غيّر <code dir=\"ltr\">ship</code> بحيث لا تقبل إلا طلبًا قيد المعالجة وتُعيد طلبًا مشحونًا. يجب أن تصبح أسطر <code dir=\"ltr\">@ts-expect-error</code> الثلاثة أخطاءً حقيقية، وألّا تحتاج <code dir=\"ltr\">describe</code> إلى <code dir=\"ltr\">!</code> بعد الآن.</p>\n","hints":["اكتب نوع كائن لكل حالة، يكون فيه <code dir=\"ltr\">status</code> قيمة نصية حرفية واحدة، واجمعها بـ <code dir=\"ltr\">|</code>.","تسمية الأشكال (<code dir=\"ltr\">ProcessingOrder</code> و<code dir=\"ltr\">ShippedOrder</code>…) تتيح لك استخدامها في توقيع <code dir=\"ltr\">ship</code>.","<code dir=\"ltr\">function ship(order: ProcessingOrder, trackingNumber: string): ShippedOrder</code>"],"explanation":"<p>صار كل شكل يحمل البيانات التي تملكها حالته بالضبط، فالكائن الحرفي الذي لا يحتوي <code dir=\"ltr\">trackingNumber</code> لا يمكن أن يكون طلبًا مشحونًا، والحقل الزائد <code dir=\"ltr\">deliveredAt</code> في طلب ملغى يُعلَّم. داخل كل <code dir=\"ltr\">case</code>، يضيّق فحص <code dir=\"ltr\">status</code> النوعَ <code dir=\"ltr\">order</code> إلى شكل واحد، فيصبح <code dir=\"ltr\">refundCents</code> من نوع <code dir=\"ltr\">number</code> عادي ولا حاجة إلى <code dir=\"ltr\">!</code>. وتحديد نوع <code dir=\"ltr\">ship</code> بـ <code dir=\"ltr\">ProcessingOrder</code> مدخلًا و<code dir=\"ltr\">ShippedOrder</code> مخرجًا ينقل القاعدة “لا تُشحن إلا الطلبات قيد المعالجة” من تعليق في مراجعة الكود إلى الـ compiler.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ: الحالات المستحيلة الثلاث كلها مرفوضة","ships":"<code dir=\"ltr\">ship</code> تُعيد طلبًا مشحونًا فيه رقم التتبّع والمعرّف نفسه","describes":"<code dir=\"ltr\">describe</code> ما زالت تصف الطلبات المرتجعة والملغاة"}},"words":633,"readMinutes":4}