{"id":"l-at-3-5","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"اكتب أنواعًا تعاودية للأشجار وJSON والتحويلات العميقة، وافهم حدود العمق في الـ compiler، وقرّر متى يكلّف النوع الذكي أكثر من الخطأ الذي يمنعه.","html":"<p>فئات المنتجات في Cartwheel تشكّل شجرة: Kitchen تحتوي Cookware، التي تحتوي Pans. يُعيد الـ API هذه الشجرة كـ JSON متداخل، وتعدّل شاشة إعدادات الدفع كائن إعداد متداخلًا بعمق. كلاهما يحتاج إلى أنواع تشير إلى نفسها. يتعامل TypeScript مع ذلك جيدًا، إلى حدٍّ ما. هذا الدرس يغطّي كيف تكتب الأنواع التعاودية، وأين تقع الحدود، والقرار التقديري الذي كان هذا القسم كله يمهّد له: متى تتوقّف.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">أنواع تشير إلى نفسها</h2>\n<p>يمكن للاسم المستعار للنوع أن يذكر نفسه داخل نوع كائن أو مصفوفة:</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\"> Category</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\">name</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\">children</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Category</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\"> Json</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> null</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Json</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\">key</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\"> Json</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span></code></pre></div>\n<p><code dir=\"ltr\">Category</code> يصف شجرة بأي عمق. و<code dir=\"ltr\">Json</code> يصف أي قيمة يمكن أن تُعيدها <code dir=\"ltr\">JSON.parse</code>، ما يجعله نوع إرجاع أفضل بكثير لمحلّل من <code dir=\"ltr\">any</code>: إنه صادق، ويجبرك على التضييق قبل الاستخدام.</p>\n<p><em>الدوال التعاودية على مستوى الأنواع</em> تتبع الفكرة نفسها. <code dir=\"ltr\">Awaited&lt;T&gt;</code>، المدمج في الـ lib منذ TypeScript 4.5، يفكّ الـ promises حتى لا يبقى منها شيء. إليك نسخة مبسّطة:</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\"> MyAwaited</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\"> MyAwaited</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\"> 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\"> Loaded</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyAwaited</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">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 style=\"color:#6E7781;--shiki-dark:#8B949E\">// Order</span></span></code></pre></div>\n<p>كل خطوة تقشر <code dir=\"ltr\">Promise</code> واحدًا وتستدعي نفسها على ما كان بداخله. وحين لا يعود النوع promise، يُعيده الفرع الكاذب وينتهي التعاود.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">التحويلات العميقة</h2>\n<p>الـ mapped types من <a href=\"/ar/lesson/advanced-typescript/l-at-3-2\">الـ Mapped Types وإعادة تسمية المفاتيح</a> سطحية. والتعاود يجعلها عميقة:</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\"> DeepReadonly</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:#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:#0550AE;--shiki-dark:#79C0FF\"> any</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span></span>\n<span class=\"line\"><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\"> object</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:#CF222E;--shiki-dark:#FF7B72\">readonly</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\"> DeepReadonly</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\">K</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:#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\"> FrozenCheckout</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> DeepReadonly</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Checkout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span></code></pre></div>\n<p>لكل حالة سببها. الأنواع الأولية تمرّ دون تغيير. والكائنات تُحوَّل، وتُعالَج كل خاصية تعاوديًا. والدوال لها فرعها الخاص لأن الـ mapped type يحتفظ بالخصائص لا بتوقيعات الاستدعاء: دون هذا الفرع، سيتحوّل handler مثل <code dir=\"ltr\">onSubmit</code> إلى كائن لا يمكنك استدعاؤه. أما المصفوفات فلا تحتاج حالة خاصة هنا، لأن الـ mapped type التماثلي المطبَّق على مصفوفة ينتج مصفوفة للقراءة فقط.</p>\n<p><code dir=\"ltr\">DeepPartial</code> هو الآخر الذي ستصادفه، عادةً لبيانات الاختبار الجاهزة (fixtures) ولتجاوزات الإعداد:</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\"> DeepPartial</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span></span>\n<span class=\"line\"><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\"> object</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\">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\"> DeepPartial</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\">K</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:#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\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> checkoutFixture</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">overrides</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> DeepPartial</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Checkout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Checkout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">checkoutFixture</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ shipping: { address: { country: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'JO'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } } });</span></span></code></pre></div>\n<p>هنا تُحفظ المصفوفات كاملة عن قصد: مصفوفة جزئية من بنود جزئية نادرًا ما تكون ما يقصده الاختبار.</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>الثقة بأن readonly تجمّد الكائنات</span></p>\n<p><code dir=\"ltr\">DeepReadonly</code> تمنع <code dir=\"ltr\">checkout.placedAt = …</code>، لكن <code dir=\"ltr\">checkout.placedAt.setFullYear(2020)</code> ما زالت تمرّ في الترجمة، لأن <code dir=\"ltr\">setFullYear</code> هي method والـ methods تُحفظ كما هي. والأمر نفسه مع <code dir=\"ltr\">Map#set</code> و<code dir=\"ltr\">Set#add</code> وأي شيء آخر يعدّل عبر method. <code dir=\"ltr\">readonly</code> وعدٌ بشأن إسناد الخصائص، لا بشأن الثبات. للقيم التي يجب ألا تتغيّر، استخدم <code dir=\"ltr\">ReadonlyMap</code> و<code dir=\"ltr\">ReadonlySet</code> والبيانات العادية، واعتبر <code dir=\"ltr\">Object.freeze</code> هي الضمانة وقت التشغيل.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">المسارات، وكلفة الذكاء</h2>\n<p>هذا هو النوع الذي يلجأ إليه الناس حين يريدون أن تُفحص <code dir=\"ltr\">get(checkout, 'shipping.address.city')</code>:</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\"> Paths</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>\n<span class=\"line\"><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:#CF222E;--shiki-dark:#FF7B72\"> &#x26;</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\"> 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:#0550AE;--shiki-dark:#79C0FF\"> object</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:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.${</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Paths</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">T</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">]></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><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:#CF222E;--shiki-dark:#FF7B72\"> &#x26;</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\"> CheckoutPath</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Paths</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;{ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">shipping</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">address</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">city</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\">country</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\">totalCents</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:#6E7781;--shiki-dark:#8B949E\">// 'shipping' | 'shipping.address' | 'shipping.address.city' | 'shipping.address.country' | 'totalCents'</span></span></code></pre></div>\n<p>إنه يعمل، وعلى نوع صغير يكون ممتعًا. لكنه أيضًا mapped type وشرط وtemplate literal وتعاود في أربعة أسطر، وكلفته تنمو مع كل حقل وكل مستوى. على كائن حالة حقيقي من 40 حقلًا فيه مصفوفات وتواريخ، قد ينتج نوع كهذا آلاف الأعضاء ويجعل كل ضغطة مفتاح في المحرّر أبطأ.</p>\n<p>ولدى TypeScript أيضًا حدود صارمة. كل تجسيد متداخل يُحسب، وبعد بضع عشرات من مستويات التعاود العادي تحصل على <em>Type instantiation is excessively deep and possibly infinite</em>. ومنذ TypeScript 4.5، يُحسَّن الـ conditional type الذي يكون استدعاؤه التعاودي في <strong>موضع ذيلي (tail position)</strong> (أي النتيجة الكاملة لفرع، كما في <code dir=\"ltr\">MyAwaited</code>)، ويمكن أن يُنفَّذ نحو 1,000 تكرار. ولا يوجد خيار في الـ compiler لرفع أيٍّ من الحدّين.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">لماذا يهمّ هذا</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>متى تتوقّف</span></p>\n<p>في عملية الترحيل التي قُدتها لكود من 400,000 سطر، حذفنا من الأنواع الذكية أكثر مما كتبنا. الاختبار الذي استخدمناه، والذي أوصي به، فيه ثلاثة أسئلة. <strong>ما الخطأ الذي يمنعه هذا النوع؟</strong> سمِّه، ويُفضَّل أن يكون خطأً رأيته في الإنتاج. <strong>هل يستطيع زميل قراءة الخطأ الذي ينتجه؟</strong> إن كانت الرسالة 30 سطرًا من الأنواع الموسَّعة، فسيلجأ الناس إلى التحويلات للخروج منها، وتضيع الحماية. <strong>كم يكلّف؟</strong> تحقّق من الوقت الذي يستغرقه المحرّر لعرض تلميح، وكيف يتغيّر زمن البناء. مفتاح <code dir=\"ltr\">string</code> بسيط مع فحص وقت التشغيل، أو خمس دوال وصول محدَّدة الأنواع، كثيرًا ما يعطي 90% من الأمان مقابل 5% من التعقيد.</p>\n</aside>\n<p>الأنواع التعاودية هي الأداة الصحيحة للبيانات التعاودية فعلًا: الأشجار، وJSON، والإعدادات المتداخلة. ولكل ما عدا ذلك، اكتب النوع الممل أولًا، ولا تلجأ إلى الذكي إلا حين يبرّره خطأ حقيقي.</p>\n<p>في التمرين ستكتب <code dir=\"ltr\">MyAwaited</code> و<code dir=\"ltr\">DeepReadonly</code> و<code dir=\"ltr\">DeepPartial</code>، وتستخدم الأخير لبيانات اختبار جاهزة. وبه يُختم قسم مستوى الأنواع. القسم 4 ينتقل إلى الأنماط: ما تبنيه الفرق المتمرّسة فعلًا بكل هذا.</p>\n","toc":[{"id":"sec-1","text":"أنواع تشير إلى نفسها","level":2},{"id":"sec-2","text":"التحويلات العميقة","level":2},{"id":"sec-3","text":"المسارات، وكلفة الذكاء","level":2}],"takeaways":["يمكن للاسم المستعار للنوع (type alias) أن يشير إلى نفسه، وهكذا تصف الأشجار وJSON وغيرها من البيانات المتداخلة.","الدوال التعاودية على مستوى الأنواع مثل <code dir=\"ltr\">DeepReadonly</code> و<code dir=\"ltr\">DeepPartial</code> تحتاج إلى حالات صريحة للدوال والمصفوفات، وإلا حوّلت أشياء لم تقصد لمسها.","يوقف TypeScript التعاود بعد بضع عشرات من المستويات المتداخلة، أو نحو 1,000 للـ conditional types ذات التعاود الذيلي، برسالة “Type instantiation is excessively deep”.","<code dir=\"ltr\">readonly</code> تمنع فقط إسناد الخصائص؛ أما methods مثل <code dir=\"ltr\">Date#setFullYear</code> أو <code dir=\"ltr\">Map#set</code> فما زالت تعدّل القيمة المعرَّفة للقراءة فقط بعمق.","قبل اعتماد نوع ذكي، اسأل ما الخطأ الذي يمنعه، وهل يستطيع زميل قراءة أخطائه، وكم يكلّف المحرّر."],"further":[{"title":"Recursive conditional types (TypeScript 4.1 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html"},{"title":"Tail-recursion elimination on conditional types (TypeScript 4.5 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-5.html"},{"title":"Awaited utility type","url":"https://www.typescriptlang.org/docs/handbook/utility-types.html#awaitedtype"}],"quiz":[{"q":"<p>بهذا التعريف، ما نوع <code dir=\"ltr\">o.lines</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\"> DeepReadonly</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:#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:#0550AE;--shiki-dark:#79C0FF\"> any</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  ?</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span></span>\n<span class=\"line\"><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\"> object</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">readonly</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\"> DeepReadonly</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\">K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]> } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">declare</span><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\"> DeepReadonly</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;{ </span><span style=\"color:#953800;--shiki-dark:#FFA657\">lines</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></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">{ sku: string }[]</code>، لأن الـ mapped types تتخطّى المصفوفات","why":"الـ mapped type التماثلي المطبَّق على مصفوفة ينتج مصفوفة، وهنا يضيف <code dir=\"ltr\">readonly</code> أيضًا."},{"text":"<code dir=\"ltr\">readonly { readonly sku: string }[]</code>","why":"صحيح. المرور على نوع مصفوفة يعطي مصفوفة للقراءة فقط، ويُجعل كل عنصر للقراءة فقط بعمق بالاستدعاء التعاودي."},{"text":"<code dir=\"ltr\">{ readonly 0: …; readonly length: number; … }</code>، كائن بمفاتيح رقمية","why":"يعامل TypeScript الـ mapped types التماثلية على المصفوفات والـ tuples معاملة خاصة، فتبقى النتيجة نوع مصفوفة."}],"answer":1},{"q":"<p>لماذا يحتاج نوع <code dir=\"ltr\">DeepReadonly</code> عادةً إلى فرع منفصل للدوال؟</p>","options":[{"text":"لأن المرور على نوع دالة يحوّله إلى كائن بلا توقيع استدعاء، فلا يعود قابلًا للاستدعاء.","why":"صحيح. الـ mapped type يحتفظ بالخصائص لا بتوقيعات الاستدعاء، فتتوقّف <code dir=\"ltr\">onSubmit</code> عن كونها قابلة للاستدعاء."},{"text":"لأن الدوال لا يمكن أن تكون للقراءة فقط في JavaScript.","why":"المشكلة في تحويل النوع، لا في التجميد وقت التشغيل؛ الـ mapped type سينزع توقيع الاستدعاء."},{"text":"لأن <code dir=\"ltr\">keyof</code> على نوع دالة خطأ.","why":"<code dir=\"ltr\">keyof</code> على نوع دالة مقبولة (وهي في الغالب <code dir=\"ltr\">never</code>). المشكلة فيما ينتجه الـ mapped type."}],"answer":0},{"q":"<p><code dir=\"ltr\">MyAwaited&lt;T&gt; = T extends Promise&lt;infer V&gt; ? MyAwaited&lt;V&gt; : T</code>. ما هو <code dir=\"ltr\">MyAwaited&lt;Promise&lt;Promise&lt;Order&gt;&gt;&gt;</code>؟</p>","options":[{"text":"<code dir=\"ltr\">Promise&lt;Order&gt;</code>","why":"هذا فكّ لمستوى واحد. الاستدعاء التعاودي يستمر ما دام النوع promise."},{"text":"<code dir=\"ltr\">Order</code>","why":"صحيح. يفكّ التعاود promise واحدًا في كل خطوة ويتوقّف حين لا يعود النوع promise."},{"text":"<code dir=\"ltr\">never</code>، لأن الـ promises المتداخلة لا توجد وقت التشغيل","why":"النوع يتعلّق بما يقوله التعريف؛ وقت التشغيل لا يحتوي الـ promise المحلول promise آخر أبدًا، ولهذا يكون فكّ كل المستويات صحيحًا."},{"text":"خطأ، لأن الـ conditional type لا يمكن أن يشير إلى نفسه","why":"الـ conditional types التعاودية مسموحة منذ TypeScript 4.1."}],"answer":1},{"q":"<p>يضيف زميل نوع <code dir=\"ltr\">Paths&lt;T&gt;</code> يولّد كل مسار بالنقاط لحالة الدفع ذات الأربعين حقلًا، فيصبح المحرّر بطيئًا. ما الإصلاح الأكثر واقعية؟</p>","options":[{"text":"ارفع حد التعاود في الـ compiler من tsconfig.","why":"لا يوجد خيار في tsconfig لحد عمق التجسيد، والبطء يتعلّق بكمية العمل لا بالحد."},{"text":"أضف مزيدًا من اختبارات الأنواع كي يُتحقَّق من النوع.","why":"الاختبارات تفحص الصحة لا الكلفة. سيبقى النوع بالبطء نفسه مع الاختبارات."},{"text":"غلّف النوع بـ <code dir=\"ltr\">NoInfer</code> كي لا يُحسب في مواضع الاستدعاء.","why":"<code dir=\"ltr\">NoInfer</code> تؤثّر فقط في مرشّحي الاستنتاج؛ ما زال يجب توسيع النوع لفحص الوسائط."},{"text":"استبدله بنوع أضيق، مثل دوال وصول محدَّدة الأنواع للمسارات القليلة التي يستخدمها الكود فعلًا.","why":"صحيح. النوع الأصغر والصريح يعطي الحماية نفسها حيث تهمّ، ولا يكلّف الـ compiler شيئًا يُذكر."}],"answer":3}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"type Checkout = {\n  id: number;\n  customer: { email: string; country: string };\n  shipping: { method: 'standard' | 'express'; address: { line1: string; city: string } };\n  lines: { sku: string; quantity: number }[];\n  onSubmit: () => void;\n};\n\ntype MyAwaited<T> = T;\ntype DeepReadonly<T> = Readonly<T>;\ntype DeepPartial<T> = Partial<T>;\n\nconst defaultCheckout: Checkout = {\n  id: 1042,\n  customer: { email: 'mona@example.com', country: 'EG' },\n  shipping: { method: 'standard', address: { line1: '12 Nile St', city: 'Cairo' } },\n  lines: [{ sku: 'TENT-2P', quantity: 1 }],\n  onSubmit: () => {},\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction mergeDeep(base: Record<string, unknown>, overrides: Record<string, unknown>): Record<string, unknown> {\n  const out: Record<string, unknown> = { ...base };\n  for (const [key, value] of Object.entries(overrides)) {\n    if (value === undefined) continue;\n    const current = out[key];\n    out[key] = isPlainObject(current) && isPlainObject(value) ? mergeDeep(current, value) : value;\n  }\n  return out;\n}\n\nfunction checkoutFixture(overrides: DeepPartial<Checkout> = {}): Checkout {\n  // mergeDeep works on untyped records; the result has Checkout's shape by construction.\n  return mergeDeep(defaultCheckout, overrides as Record<string, unknown>) as Checkout;\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<MyAwaited<Promise<Promise<Checkout>>>, Checkout>>;\ntype T2 = Expect<Equal<MyAwaited<string>, string>>;\n\ntype Frozen = DeepReadonly<Checkout>;\ntype T3 = Expect<Equal<Frozen['shipping']['address'], { readonly line1: string; readonly city: string }>>;\ntype T4 = Expect<Equal<Frozen['lines'], readonly { readonly sku: string; readonly quantity: number }[]>>;\ntype T5 = Expect<Equal<Frozen['onSubmit'], () => void>>;\n\ntype T6 = Expect<Equal<DeepPartial<Checkout>['shipping'], { method?: 'standard' | 'express'; address?: { line1?: string; city?: string } } | undefined>>;\ntype T7 = Expect<Equal<DeepPartial<Checkout>['lines'], { sku: string; quantity: number }[] | undefined>>;\n\nfunction mutationTest(frozen: Frozen) {\n  // @ts-expect-error: nested properties are readonly\n  frozen.shipping.address.city = 'Amman';\n}\n\nconst fixture = checkoutFixture({ shipping: { address: { city: 'Amman' } }, customer: { country: 'JO' } });\nconsole.log(fixture.shipping.address, fixture.customer);\n"},"solution":{"ts":"type Checkout = {\n  id: number;\n  customer: { email: string; country: string };\n  shipping: { method: 'standard' | 'express'; address: { line1: string; city: string } };\n  lines: { sku: string; quantity: number }[];\n  onSubmit: () => void;\n};\n\ntype MyAwaited<T> = T extends Promise<infer V> ? MyAwaited<V> : T;\n\ntype DeepReadonly<T> = T extends (...args: any[]) => any\n  ? T\n  : T extends object\n    ? { readonly [K in keyof T]: DeepReadonly<T[K]> }\n    : T;\n\ntype DeepPartial<T> = T extends readonly unknown[]\n  ? T\n  : T extends object\n    ? { [K in keyof T]?: DeepPartial<T[K]> }\n    : T;\n\nconst defaultCheckout: Checkout = {\n  id: 1042,\n  customer: { email: 'mona@example.com', country: 'EG' },\n  shipping: { method: 'standard', address: { line1: '12 Nile St', city: 'Cairo' } },\n  lines: [{ sku: 'TENT-2P', quantity: 1 }],\n  onSubmit: () => {},\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction mergeDeep(base: Record<string, unknown>, overrides: Record<string, unknown>): Record<string, unknown> {\n  const out: Record<string, unknown> = { ...base };\n  for (const [key, value] of Object.entries(overrides)) {\n    if (value === undefined) continue;\n    const current = out[key];\n    out[key] = isPlainObject(current) && isPlainObject(value) ? mergeDeep(current, value) : value;\n  }\n  return out;\n}\n\nfunction checkoutFixture(overrides: DeepPartial<Checkout> = {}): Checkout {\n  // mergeDeep works on untyped records; the result has Checkout's shape by construction.\n  return mergeDeep(defaultCheckout, overrides as Record<string, unknown>) as Checkout;\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<MyAwaited<Promise<Promise<Checkout>>>, Checkout>>;\ntype T2 = Expect<Equal<MyAwaited<string>, string>>;\n\ntype Frozen = DeepReadonly<Checkout>;\ntype T3 = Expect<Equal<Frozen['shipping']['address'], { readonly line1: string; readonly city: string }>>;\ntype T4 = Expect<Equal<Frozen['lines'], readonly { readonly sku: string; readonly quantity: number }[]>>;\ntype T5 = Expect<Equal<Frozen['onSubmit'], () => void>>;\n\ntype T6 = Expect<Equal<DeepPartial<Checkout>['shipping'], { method?: 'standard' | 'express'; address?: { line1?: string; city?: string } } | undefined>>;\ntype T7 = Expect<Equal<DeepPartial<Checkout>['lines'], { sku: string; quantity: number }[] | undefined>>;\n\nfunction mutationTest(frozen: Frozen) {\n  // @ts-expect-error: nested properties are readonly\n  frozen.shipping.address.city = 'Amman';\n}\n\nconst fixture = checkoutFixture({ shipping: { address: { city: 'Amman' } }, customer: { country: 'JO' } });\nconsole.log(fixture.shipping.address, fixture.customer);\n"},"checks":[{"id":"types","typecheck":true},{"id":"fixture","test":"const f = checkoutFixture({ shipping: { address: { city: 'Amman' } } }); return f.shipping.address.city === 'Amman' && f.shipping.address.line1 === '12 Nile St' && f.shipping.method === 'standard' && f.lines.length === 1;"}],"title":"تعمّق، بحذر","prompt":"<p>نفّذ ثلاثة أنواع تعاودية (لا تستخدم <code dir=\"ltr\">Awaited</code> المدمج):</p>\n<ul>\n<li><code dir=\"ltr\">MyAwaited&lt;T&gt;</code>: فكّ الـ promises المتداخلة حتى لا يعود النوع promise.</li>\n<li><code dir=\"ltr\">DeepReadonly&lt;T&gt;</code>: اجعل كل خاصية في كل مستوى <code dir=\"ltr\">readonly</code>، وحوّل المصفوفات إلى مصفوفات للقراءة فقط من عناصر للقراءة فقط بعمق، واترك الدوال دون مساس (قابلة للاستدعاء).</li>\n<li><code dir=\"ltr\">DeepPartial&lt;T&gt;</code>: اجعل كل خاصية في كل مستوى اختيارية، لكن أبقِ المصفوفات كما هي (البيانات الجاهزة تستبدل المصفوفة كاملة).</li>\n</ul>\n<p><code dir=\"ltr\">checkoutFixture</code> تدمج التجاوزات وقت التشغيل أصلًا؛ وحين يصبح <code dir=\"ltr\">DeepPartial</code> صحيحًا، يمرّ التجاوز المتداخل في المثال في الترجمة.</p>\n","hints":["كل نوع شرطٌ يستدعي نفسه في الفرع الذي ينزل مستوى أعمق.","رتّب الحالات: الدوال أولًا (أعِد <code dir=\"ltr\">T</code>)، ثم الكائنات (حوّل وعاوِد)، ثم كل ما عدا ذلك (أعِد <code dir=\"ltr\">T</code>). المصفوفات كائنات، والـ mapped type التماثلي يُبقيها مصفوفات.","في <code dir=\"ltr\">DeepPartial</code>، أضف حالة أولى <code dir=\"ltr\">T extends readonly unknown[] ? T : …</code> كي لا تُجعل المصفوفات جزئية."],"explanation":"<p>يعاود <code dir=\"ltr\">MyAwaited</code> في موضع ذيلي، مقشّرًا promise واحدًا في كل خطوة. ويحتاج <code dir=\"ltr\">DeepReadonly</code> إلى فرع للدوال لأن الـ mapped type يحتفظ بالخصائص ويُسقط توقيعات الاستدعاء، فتتوقّف <code dir=\"ltr\">onSubmit</code> لولاه عن كونها قابلة للاستدعاء؛ ولا تحتاج المصفوفات حالة إضافية لأن المرور على نوع مصفوفة ينتج مصفوفة للقراءة فقط. ويتوقّف <code dir=\"ltr\">DeepPartial</code> عند المصفوفات عن قصد، وهذا يطابق طريقة معاملة <code dir=\"ltr\">mergeDeep</code> لها: التجاوز يستبدل المصفوفة كاملة. والتحويلات في <code dir=\"ltr\">checkoutFixture</code> محلّية ومشروحة بتعليقات: الدمج يعمل على سجلّات بلا أنواع، ويُستعاد نوع البيانات الجاهزة مرة واحدة عند الحدود.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ، بما فيها كل اختبارات الأنواع","fixture":"<code dir=\"ltr\">checkoutFixture</code> تدمج التجاوزات المتداخلة في القيم الافتراضية"}},"words":627,"readMinutes":4}