{"id":"l-at-3-3","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"اكتب conditional types تختار بين الأنواع، وتوقّع كيف تتوزّع على الـ unions، واستخرج أنواعًا من أنواع أخرى بـ infer، معيدًا بناء Exclude وExtract وReturnType.","html":"<p>في <a href=\"/ar/lesson/advanced-typescript/l-at-2-4\">تصميم عميل API بأنواع دقيقة</a>، أجبرت المسارات التي لا مدخل لها المستدعين على كتابة <code dir=\"ltr\">call('GET /me', {})</code>. الإصلاح يحتاج إلى نوع <em>يتّخذ قرارًا</em>: “إن لم يكن في مدخل هذا المسار حقول إلزامية، فاجعل الوسيط اختياريًا؛ وإلا فاطلبه”. الـ mapped types تمرّ على المفاتيح. والـ conditional types (الأنواع الشرطية) تقرّر.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">العامل الثلاثي على مستوى الأنواع</h2>\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\"> IsArray</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:#CF222E;--shiki-dark:#FF7B72\">=</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:#0550AE;--shiki-dark:#79C0FF\"> unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> true</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> false</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\"> A</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> IsArray</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]>; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// true</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</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\"> IsArray</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// false</span></span></code></pre></div>\n<p><code dir=\"ltr\">T extends U ? X : Y</code> تُقرأ مثل العامل الثلاثي: إن كان <code dir=\"ltr\">T</code> قابلًا للإسناد إلى <code dir=\"ltr\">U</code> فالنتيجة <code dir=\"ltr\">X</code>، وإلا <code dir=\"ltr\">Y</code>. و<code dir=\"ltr\">extends</code> هنا هي فحص قابلية الإسناد نفسه الموجود في كل مكان آخر في TypeScript: “هل <code dir=\"ltr\">T</code> مجموعة جزئية من <code dir=\"ltr\">U</code>؟”. وحين يكون <code dir=\"ltr\">T</code> معاملًا generic غير معروف بعد، <strong>يؤجّل</strong> TypeScript الشرط ويُبقيه كما هو حتى يصل نوع محدّد.</p>\n<p>إليك إصلاح عميل الـ API. <code dir=\"ltr\">{} extends Input</code> صادقة تمامًا حين تكون كل خصائص <code dir=\"ltr\">Input</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\"> CallArgs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">R</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Routes</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:#CF222E;--shiki-dark:#FF7B72\">extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Routes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">R</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">][</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'input'</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\">input</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Routes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">R</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">][</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'input'</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\">input</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Routes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">R</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">][</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'input'</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\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> call</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">R</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Routes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">route</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> R</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CallArgs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">R</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Routes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">R</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">][</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'output'</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\">call</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'GET /me'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);                    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ok: input is {}</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">call</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'GET /orders'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);                </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ok: every filter is optional</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">call</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'GET /orders/:id'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);            </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Expected 2 arguments, but got 1.</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">call</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'GET /orders/:id'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, { id: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ok</span></span></code></pre></div>\n<p>ينتج الشرط نوع <em>tuple</em>، وينشره معامل البقية (rest parameter) <code dir=\"ltr\">...args</code> في قائمة المعاملات. وعناصر الـ tuple الموسومة (<code dir=\"ltr\">input?:</code>) تُبقي اسم المعامل في التلميحات. ولم يتغيّر موضع استدعاء واحد.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">التوزيع على الـ unions</h2>\n<p>للـ conditional types سلوك واحد يفاجئ الجميع. حين يكون النوع المفحوص <strong>معامل نوع مجرّدًا (naked type parameter)</strong> وتمرّر إليه union، يُنفَّذ الشرط مرة لكل عضو وتُجمع النتائج:</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\"> ToArray</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:#CF222E;--shiki-dark:#FF7B72\">=</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\"> unknown</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\"> never</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> X</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ToArray</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// string[] | number[], not (string | number)[]</span></span></code></pre></div>\n<p>يُسمّى هذا <strong>التوزيع (distribution)</strong>، وهو المحرّك وراء عدد من الـ utility types. إليك <code dir=\"ltr\">Exclude</code> و<code dir=\"ltr\">Extract</code>، كما تعرّفهما الـ lib بالضبط:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyExclude</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\">U</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:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> U</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyExtract</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\">U</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:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> U</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Active</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyExclude</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\">'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 style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'processing' | 'shipped' | 'delivered'</span></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\"> MyExtract</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, { </span><span 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:#6E7781;--shiki-dark:#8B949E\">// the ShippedOrder variant</span></span></code></pre></div>\n<p>كل عضو يمرّ عبر الشرط وحده. والأعضاء المحوَّلة إلى <code dir=\"ltr\">never</code> تختفي، لأن <code dir=\"ltr\">never</code> هو الـ union الفارغ. و<code dir=\"ltr\">MyExtract&lt;Order, { status: 'shipped' }&gt;</code> هي أنظف طريقة لتسمية شكل واحد من discriminated union.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 240\" role=\"img\" aria-labelledby=\"l-at-3-3-t1\"><defs><marker id=\"arrow-l-at-3-3-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-3-3-t1\">الـ union من processing وshipped وcancelled يدخل Exclude مع cancelled. يُقسم إلى ثلاثة أعضاء. يُختبر كل عضو مقابل cancelled: يبقى processing وshipped، ويصبح cancelled هو never. ثم تُجمع النتائج في processing أو shipped.</title>\n  <rect class=\"d-box-accent\" x=\"10\" y=\"96\" width=\"150\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"85\" y=\"125\" text-anchor=\"middle\">A | B | C</text>\n  <path class=\"d-arrow\" d=\"M160 110 L224 50\" marker-end=\"url(#arrow-l-at-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M160 120 L224 120\" marker-end=\"url(#arrow-l-at-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M160 130 L224 190\" marker-end=\"url(#arrow-l-at-3-3-1)\"/>\n  <rect class=\"d-box\" x=\"228\" y=\"28\" width=\"230\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"343\" y=\"55\" text-anchor=\"middle\">'processing' ext C? → يبقى</text>\n  <rect class=\"d-box\" x=\"228\" y=\"98\" width=\"230\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"343\" y=\"125\" text-anchor=\"middle\">'shipped' ext C? → يبقى</text>\n  <rect class=\"d-box-warn\" x=\"228\" y=\"168\" width=\"230\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"343\" y=\"195\" text-anchor=\"middle\">'cancelled' ext C? → never</text>\n  <path class=\"d-arrow\" d=\"M458 50 L522 110\" marker-end=\"url(#arrow-l-at-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M458 120 L522 120\" marker-end=\"url(#arrow-l-at-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M458 190 L522 130\" marker-end=\"url(#arrow-l-at-3-3-1)\"/>\n  <rect class=\"d-box-success\" x=\"526\" y=\"96\" width=\"164\" height=\"48\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"608\" y=\"125\" text-anchor=\"middle\">'processing' | 'shipped'</text>\n</svg><figcaption>الشرط التوزيعي يقسم الـ union، ويُنفَّذ لكل عضو، ثم يجمع من جديد</figcaption></figure>\n<p>لإيقاف التوزيع، غلّف الطرفين في tuple من عنصر واحد. <code dir=\"ltr\">[T]</code> ليس معامل نوع مجرّدًا، فيُفحص الـ 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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ToArrayWhole</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:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</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\">extends</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#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\"> never</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\"> Y</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ToArrayWhole</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// (string | number)[]</span></span></code></pre></div>\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>نسيان أن never هو union فارغ</span></p>\n<p>الشرط التوزيعي الذي يُعطى <code dir=\"ltr\">never</code> يُعيد <code dir=\"ltr\">never</code>، دون تقييم أيٍّ من الفرعين، لأنه لا توجد أعضاء لينفَّذ عليها. لذا فإن <code dir=\"ltr\">IsString&lt;never&gt;</code> هو <code dir=\"ltr\">never</code>، لا <code dir=\"ltr\">true</code> ولا <code dir=\"ltr\">false</code>. إن كتبت اختبارات أنواع لـ conditional type، فضمّن <code dir=\"ltr\">never</code> وunion بين المدخلات، واستخدم <code dir=\"ltr\">[T] extends [never]</code> حين تحتاج إلى اكتشاف <code dir=\"ltr\">never</code> نفسه.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">infer: التقط جزءًا من نوع</h2>\n<p>داخل جملة <code dir=\"ltr\">extends</code>، تعلن <code dir=\"ltr\">infer X</code> متغيّر نوع يلتقط ما يقع في ذلك الموضع، إن طابق النمط. وهكذا تعرّف الـ lib النوع <code dir=\"ltr\">ReturnType</code> في جوهره (نسخة الـ lib تقيّد <code dir=\"ltr\">F</code> أيضًا بنوع دالة وترجع إلى <code dir=\"ltr\">any</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyReturnType</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">F</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> F</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> any</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> R</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> R</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Loaded</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyReturnType</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">typeof</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> loadOrder>; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Promise&#x3C;Order></span></span></code></pre></div>\n<p>الحيلة نفسها تستخرج أنواعًا من أي شيء له بنية:</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\"> ElementOf</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:#CF222E;--shiki-dark:#FF7B72\">=</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:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> E</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> E</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> UnwrapPromise</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:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> V</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> V</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FirstArg</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">F</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> F</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\">first</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> A</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">rest</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> any</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> any</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> A</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p>ويمكن أن تحمل <code dir=\"ltr\">infer</code> قيدًا أيضًا، كما في <code dir=\"ltr\">infer S extends string</code>، فلا تطابق إلا حين يكون النوع الملتقَط نصًا، وتعطيك <code dir=\"ltr\">S</code> مضيَّقًا مسبقًا. ستحتاج إلى ذلك في الدرس القادم، حين تلتقط أجزاءً من الـ literal types النصية.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">تصفية المفاتيح حسب نوع القيمة</h2>\n<p>اجمع mapped type وشرطًا ووصولًا بالفهرس، وتستطيع اختيار المفاتيح حسب ماهية قيمها:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"7\"><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\"> KeysOfType</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\">V</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\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</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:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> V</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }[</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</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\"> Line</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">sku</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\">quantity</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\">unitPriceCents</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> NumericField</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> KeysOfType</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Line</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 style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'quantity' | 'unitPriceCents'</span></span></code></pre></div>\n<p>يحوّل الـ mapped type كل خاصية إلى اسم مفتاحها أو إلى <code dir=\"ltr\">never</code>، ثم تجمع <code dir=\"ltr\">[keyof T]</code> القيم في union، تختفي فيه قيم <code dir=\"ltr\">never</code>. يُقرأ بالمقلوب في المرة الأولى؛ اكتبه مرة، وسمِّه جيدًا، وأعِد استخدامه. والآن دالة ترتيب نوعها <code dir=\"ltr\">sortBy(lines, key: KeysOfType&lt;Line, number&gt;)</code> ترفض الترتيب حسب <code dir=\"ltr\">sku</code>.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">أنواع الإرجاع الشرطية: تعامل معها بحذر</h2>\n<p>من المغري إعطاء دالة نوع إرجاع شرطيًا، مثل <code dir=\"ltr\">function format&lt;T extends number | number[]&gt;(x: T): T extends number ? string : string[]</code>. يحصل المستدعون على أنواع دقيقة، لكن التنفيذ لا يحصل عليها: داخل الدالة، يبقى <code dir=\"ltr\">T</code> مجهولًا، ويبقى الشرط مؤجّلًا، ولا يستطيع TypeScript أن يتحقّق من أن <code dir=\"ltr\">return x.toFixed(2)</code> تطابقه. فينتهي بك الأمر إلى كتابة <code dir=\"ltr\">as any</code> أو <code dir=\"ltr\">as T extends number ? string : string[]</code> على كل جملة عودة، أي أن أدقّ كود في الدالة هو الكود الذي لا يفحصه أحد.</p>\n<p>لهذا فضّل القسم السابق الـ overloads أو الدوال المنفصلة لهذه المهمة. تتألّق الـ conditional types في الأنواع <em>المشتقّة</em> (<code dir=\"ltr\">CallArgs</code> و<code dir=\"ltr\">Extract</code> و<code dir=\"ltr\">KeysOfType</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>اختبر الـ conditional types كما تختبر الدوال</span></p>\n<p>الـ conditional types برامج صغيرة لها حالات حدّية. اختبرها كما تفعل التمارين: حالة عادية، وunion، و<code dir=\"ltr\">never</code>، و<code dir=\"ltr\">any</code>، وخاصية اختيارية. خمسة أسطر <code dir=\"ltr\">Expect&lt;Equal&lt;…&gt;&gt;</code> تلتقط معظم المفاجآت قبل أن يلتقطها زملاؤك.</p>\n</aside>\n<p>في التمرين ستعيد بناء <code dir=\"ltr\">Exclude</code> و<code dir=\"ltr\">Extract</code> و<code dir=\"ltr\">ReturnType</code> وتستخدمها على أنواع الطلبات في Cartwheel. والدرس القادم يطبّق الشروط و<code dir=\"ltr\">infer</code> على النصوص.</p>\n","toc":[{"id":"sec-1","text":"العامل الثلاثي على مستوى الأنواع","level":2},{"id":"sec-2","text":"التوزيع على الـ unions","level":2},{"id":"sec-3","text":"infer: التقط جزءًا من نوع","level":2},{"id":"sec-4","text":"تصفية المفاتيح حسب نوع القيمة","level":2},{"id":"sec-5","text":"أنواع الإرجاع الشرطية: تعامل معها بحذر","level":2}],"takeaways":["<code dir=\"ltr\">T extends U ? X : Y</code> تختار <code dir=\"ltr\">X</code> حين يكون <code dir=\"ltr\">T</code> قابلًا للإسناد إلى <code dir=\"ltr\">U</code>، و<code dir=\"ltr\">Y</code> في غير ذلك؛ ومع <code dir=\"ltr\">T</code> من نوع generic، ينتظر الاختيار حتى يُعرف <code dir=\"ltr\">T</code>.","الشرط على معامل نوع مجرّد يتوزّع على الـ unions، فيُنفَّذ مرة لكل عضو ثم تُجمع النتائج.","غلّف الطرفين بأقواس مربّعة، <code dir=\"ltr\">[T] extends [U]</code>، لتختبر الـ union ككل بدل التوزيع.","<code dir=\"ltr\">infer</code> تعلن متغيّر نوع داخل جملة <code dir=\"ltr\">extends</code> وتلتقط ما يطابقه، وهكذا يعمل <code dir=\"ltr\">ReturnType</code> و<code dir=\"ltr\">Awaited</code>.","تحويل كل مفتاح إلى نفسه أو إلى <code dir=\"ltr\">never</code> ثم الفهرسة بـ <code dir=\"ltr\">[keyof T]</code> يصفّي المفاتيح حسب نوع قيمتها."],"further":[{"title":"Conditional Types","url":"https://www.typescriptlang.org/docs/handbook/2/conditional-types.html"},{"title":"Utility Types (Exclude, Extract, ReturnType)","url":"https://www.typescriptlang.org/docs/handbook/utility-types.html"}],"quiz":[{"q":"<p><code dir=\"ltr\">type ToArray&lt;T&gt; = T extends unknown ? T[] : never</code>. ما هو <code dir=\"ltr\">ToArray&lt;string | number&gt;</code>؟</p>","options":[{"text":"<code dir=\"ltr\">(string | number)[]</code>","why":"هذه هي النتيجة غير التوزيعية، وتحصل عليها بـ <code dir=\"ltr\">[T] extends [unknown]</code>."},{"text":"<code dir=\"ltr\">never</code>","why":"كلا العضوين قابل للإسناد إلى <code dir=\"ltr\">unknown</code>، فلا يأخذ أيٌّ منهما فرع <code dir=\"ltr\">never</code>."},{"text":"<code dir=\"ltr\">unknown[]</code>","why":"الفرع الصادق يستخدم <code dir=\"ltr\">T</code>، وهو كل عضو بدوره، لا القيد <code dir=\"ltr\">unknown</code>."},{"text":"<code dir=\"ltr\">string[] | number[]</code>","why":"صحيح. <code dir=\"ltr\">T</code> معامل نوع مجرّد، فيُنفَّذ الشرط مرة لـ <code dir=\"ltr\">string</code> ومرة لـ <code dir=\"ltr\">number</code>، وتُجمع النتائج."}],"answer":3},{"q":"<p>أيّ نوع يعطيك الشكل المشحون من الـ union المسمّى <code dir=\"ltr\">Order</code>؟</p>","options":[{"text":"<code dir=\"ltr\">Extract&lt;Order, { status: 'shipped' }&gt;</code>","why":"صحيح. <code dir=\"ltr\">Extract</code> تُبقي أعضاء الـ union القابلة للإسناد إلى <code dir=\"ltr\">{ status: 'shipped' }</code>، وهذا بالضبط الشكل المشحون."},{"text":"<code dir=\"ltr\">Exclude&lt;Order, { status: 'shipped' }&gt;</code>","why":"<code dir=\"ltr\">Exclude</code> تفعل العكس: تُزيل الشكل المشحون وتُبقي الباقي."},{"text":"<code dir=\"ltr\">Order['shipped']</code>","why":"الوصول بالفهرس يبحث عن خاصية اسمها <code dir=\"ltr\">shipped</code>، و<code dir=\"ltr\">Order</code> ليس فيه مثلها."},{"text":"<code dir=\"ltr\">Order extends { status: 'shipped' } ? Order : never</code>","why":"<code dir=\"ltr\">Order</code> هنا نوع محدّد، لا معامل نوع، فلا يتوزّع شيء. والـ union كله غير قابل للإسناد إلى الشكل المشحون، فتحصل على <code dir=\"ltr\">never</code>."}],"answer":0},{"q":"<p>ما هو <code dir=\"ltr\">R</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\"> MyReturnType</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">F</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> F</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> any</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> infer</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> R</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> R</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</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\"> R</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyReturnType</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>>;</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">Order</code>","why":"<code dir=\"ltr\">infer R</code> تلتقط نوع الإرجاع كما هو مكتوب. فكّ الـ promise يحتاج خطوة ثانية، مثل <code dir=\"ltr\">Awaited</code>."},{"text":"<code dir=\"ltr\">Promise&lt;Order&gt;</code>","why":"صحيح. نوع الدالة يطابق النمط، وتلتقط <code dir=\"ltr\">R</code> نوع إرجاعها بالضبط."},{"text":"<code dir=\"ltr\">never</code>","why":"لا تحصل على <code dir=\"ltr\">never</code> إلا حين لا يكون <code dir=\"ltr\">F</code> نوع دالة. وهنا هو يطابق."}],"answer":1},{"q":"<p><code dir=\"ltr\">type IsString&lt;T&gt; = T extends string ? true : false</code>. ما هو <code dir=\"ltr\">IsString&lt;never&gt;</code>؟</p>","options":[{"text":"<code dir=\"ltr\">true</code>، لأن <code dir=\"ltr\">never</code> قابل للإسناد إلى كل شيء","why":"هذا صحيح في فحص غير توزيعي مثل <code dir=\"ltr\">[never] extends [string]</code>. أما الشرط التوزيعي فلا يصل إلى هذا الحد أصلًا."},{"text":"<code dir=\"ltr\">false</code>، لأن <code dir=\"ltr\">never</code> ليس نصًا","why":"لا يُقيَّم الشرط إطلاقًا؛ لا توجد أعضاء union لينفَّذ عليها."},{"text":"<code dir=\"ltr\">never</code>، لأن التوزيع على union فارغ ينتج union فارغًا","why":"صحيح. <code dir=\"ltr\">never</code> هو الـ union الفارغ، فيمرّ الشرط التوزيعي على صفر من الأعضاء ويُعيد <code dir=\"ltr\">never</code>."}],"answer":2}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype Order =\n  | { id: number; status: 'processing' }\n  | { id: number; status: 'shipped'; trackingNumber: string }\n  | { id: number; status: 'delivered'; trackingNumber: string; deliveredAt: string }\n  | { id: number; status: 'cancelled'; cancelReason: string }\n  | { id: number; status: 'returned'; trackingNumber: string; deliveredAt: string; refundCents: number };\ntype Line = { sku: string; quantity: number; unitPriceCents: number; giftWrapped: boolean };\ndeclare function loadOrder(id: number): Promise<Order>;\n\ntype MyExclude<T, U> = T;\ntype MyExtract<T, U> = T;\ntype MyReturnType<F> = unknown;\ntype UnwrapPromise<T> = T;\n\n// The Order variant(s) whose status is S.\ntype OrderWithStatus<S extends OrderStatus> = Order;\n\n// The keys of T whose values are assignable to V.\ntype KeysOfType<T, V> = keyof T;\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<MyExclude<OrderStatus, 'cancelled' | 'returned'>, 'processing' | 'shipped' | 'delivered'>>;\ntype T2 = Expect<Equal<MyExtract<OrderStatus, 'shipped' | 'refunded'>, 'shipped'>>;\ntype T3 = Expect<Equal<MyReturnType<typeof loadOrder>, Promise<Order>>>;\ntype T4 = Expect<Equal<MyReturnType<() => void>, void>>;\ntype T5 = Expect<Equal<UnwrapPromise<Promise<Order>>, Order>>;\ntype T6 = Expect<Equal<UnwrapPromise<number>, number>>;\ntype T7 = Expect<Equal<OrderWithStatus<'cancelled'>, { id: number; status: 'cancelled'; cancelReason: string }>>;\ntype T8 = Expect<Equal<OrderWithStatus<'delivered' | 'returned'>['deliveredAt'], string>>;\ntype T9 = Expect<Equal<KeysOfType<Line, number>, 'quantity' | 'unitPriceCents'>>;\ntype T10 = Expect<Equal<KeysOfType<Line, boolean>, 'giftWrapped'>>;\n\nconst shippedOnly: OrderWithStatus<'shipped'> = { id: 1042, status: 'shipped', trackingNumber: '1Z-999' };\nconsole.log(shippedOnly.trackingNumber);\n"},"solution":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype Order =\n  | { id: number; status: 'processing' }\n  | { id: number; status: 'shipped'; trackingNumber: string }\n  | { id: number; status: 'delivered'; trackingNumber: string; deliveredAt: string }\n  | { id: number; status: 'cancelled'; cancelReason: string }\n  | { id: number; status: 'returned'; trackingNumber: string; deliveredAt: string; refundCents: number };\ntype Line = { sku: string; quantity: number; unitPriceCents: number; giftWrapped: boolean };\ndeclare function loadOrder(id: number): Promise<Order>;\n\ntype MyExclude<T, U> = T extends U ? never : T;\ntype MyExtract<T, U> = T extends U ? T : never;\ntype MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never;\ntype UnwrapPromise<T> = T extends Promise<infer V> ? V : T;\n\n// The Order variant(s) whose status is S.\ntype OrderWithStatus<S extends OrderStatus> = MyExtract<Order, { status: S }>;\n\n// The keys of T whose values are assignable to V.\ntype KeysOfType<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<MyExclude<OrderStatus, 'cancelled' | 'returned'>, 'processing' | 'shipped' | 'delivered'>>;\ntype T2 = Expect<Equal<MyExtract<OrderStatus, 'shipped' | 'refunded'>, 'shipped'>>;\ntype T3 = Expect<Equal<MyReturnType<typeof loadOrder>, Promise<Order>>>;\ntype T4 = Expect<Equal<MyReturnType<() => void>, void>>;\ntype T5 = Expect<Equal<UnwrapPromise<Promise<Order>>, Order>>;\ntype T6 = Expect<Equal<UnwrapPromise<number>, number>>;\ntype T7 = Expect<Equal<OrderWithStatus<'cancelled'>, { id: number; status: 'cancelled'; cancelReason: string }>>;\ntype T8 = Expect<Equal<OrderWithStatus<'delivered' | 'returned'>['deliveredAt'], string>>;\ntype T9 = Expect<Equal<KeysOfType<Line, number>, 'quantity' | 'unitPriceCents'>>;\ntype T10 = Expect<Equal<KeysOfType<Line, boolean>, 'giftWrapped'>>;\n\nconst shippedOnly: OrderWithStatus<'shipped'> = { id: 1042, status: 'shipped', trackingNumber: '1Z-999' };\nconsole.log(shippedOnly.trackingNumber);\n"},"checks":[{"id":"types","typecheck":true}],"title":"ابنِ Exclude وExtract ورفاقهما","prompt":"<p>نفّذ هذه الأنواع بالـ conditional types (لا تستخدم <code dir=\"ltr\">Exclude</code> أو <code dir=\"ltr\">Extract</code> أو <code dir=\"ltr\">ReturnType</code> أو <code dir=\"ltr\">Awaited</code> المدمجة):</p>\n<ul>\n<li><code dir=\"ltr\">MyExclude&lt;T, U&gt;</code>: أعضاء <code dir=\"ltr\">T</code> غير القابلة للإسناد إلى <code dir=\"ltr\">U</code>.</li>\n<li><code dir=\"ltr\">MyExtract&lt;T, U&gt;</code>: أعضاء <code dir=\"ltr\">T</code> القابلة للإسناد إلى <code dir=\"ltr\">U</code>.</li>\n<li><code dir=\"ltr\">MyReturnType&lt;F&gt;</code>: نوع إرجاع نوع دالة.</li>\n<li><code dir=\"ltr\">UnwrapPromise&lt;T&gt;</code>: القيمة داخل <code dir=\"ltr\">Promise</code>، أو <code dir=\"ltr\">T</code> نفسه إن لم يكن promise.</li>\n<li><code dir=\"ltr\">OrderWithStatus&lt;S&gt;</code>: شكل (أو أشكال) <code dir=\"ltr\">Order</code> الذي يكون <code dir=\"ltr\">status</code> فيه هو <code dir=\"ltr\">S</code>.</li>\n<li><code dir=\"ltr\">KeysOfType&lt;T, V&gt;</code>: مفاتيح <code dir=\"ltr\">T</code> التي تكون قيمها قابلة للإسناد إلى <code dir=\"ltr\">V</code>.</li>\n</ul>\n","hints":["<code dir=\"ltr\">MyExclude</code> و<code dir=\"ltr\">MyExtract</code> شرطان توزيعيان من سطر واحد: أحدهما يُعيد <code dir=\"ltr\">never</code> في الفرع الصادق، والآخر في الفرع الكاذب.","استخدم <code dir=\"ltr\">infer</code> لالتقاط جزء من نوع: <code dir=\"ltr\">F extends (...args: any[]) =&gt; infer R ? R : never</code>.","<code dir=\"ltr\">OrderWithStatus</code> هو <code dir=\"ltr\">MyExtract&lt;Order, { status: S }&gt;</code>. وفي <code dir=\"ltr\">KeysOfType</code>، حوّل كل مفتاح إلى نفسه أو إلى <code dir=\"ltr\">never</code>، ثم افهرس النتيجة بـ <code dir=\"ltr\">[keyof T]</code>."],"explanation":"<p>تعتمد <code dir=\"ltr\">MyExclude</code> و<code dir=\"ltr\">MyExtract</code> على التوزيع: <code dir=\"ltr\">T</code> معامل نوع مجرّد، فيُختبر كل عضو في الـ union منفردًا وتختفي نتائج <code dir=\"ltr\">never</code>. والتوزيع نفسه يجعل <code dir=\"ltr\">OrderWithStatus&lt;'delivered' | 'returned'&gt;</code> يعطي الشكلين كليهما، ولهذا تعمل <code dir=\"ltr\">['deliveredAt']</code> عليه. وتلتقط <code dir=\"ltr\">infer</code> نوع الإرجاع وقيمة الـ promise. أما <code dir=\"ltr\">KeysOfType</code> فتبني كائنًا قيمه أسماء مفاتيح أو <code dir=\"ltr\">never</code>، ثم تقرأ كل قيمه دفعة واحدة بـ <code dir=\"ltr\">[keyof T]</code>.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ: اختبارات الأنواع العشرة كلها ناجحة"}},"words":685,"readMinutes":4}