{"id":"l-at-3-2","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"حوّل كل خصائص النوع دفعة واحدة بالـ mapped types، وأضف معدِّلات readonly والاختيارية أو أزلها، وأعِد تسمية المفاتيح أو احذفها بجمل as، معيدًا بناء Partial وPick وOmit في الطريق.","html":"<p>في صفحة الدفع في Cartwheel نموذج للعنوان. نوع العنوان بسيط:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> 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\">line1</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\">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\">postcode</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span></code></pre></div>\n<p>لكن النموذج يحتاج إلى أكثر من العنوان. يحتاج لكل حقل إلى قيمته الحالية، وخطأ التحقّق، وما إذا كان المستخدم قد لمسه. ويحتاج الـ endpoint الخاص بـ PATCH إلى نسخة كل حقولها اختيارية. وتحتاج شاشة المراجعة إلى نسخة مجمّدة. يمكنك كتابة ثلاثة أنواع أخرى يدويًا، كلٌّ منها يكرّر أسماء الحقول الأربعة، وتحديثها كلها كلما تغيّر <code dir=\"ltr\">Address</code>. أو يمكنك كتابة كلٍّ منها مرة واحدة، كتحويل. هذا هو الـ <strong>mapped type (النوع المحوَّل)</strong>: حلقة تكرار على المفاتيح، على مستوى الأنواع.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الحلقة</h2>\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\"> FieldState</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:#1F2328;--shiki-dark:#E6EDF3\">]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">error</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\"> null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">touched</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#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\"> AddressForm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FieldState</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Address</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   line1: { value: string; error: string | null; touched: boolean };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   city: { value: string; … };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   country: { value: string; … };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   postcode?: { value: string | undefined; … };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// }</span></span></code></pre></div>\n<p>اقرأ <code dir=\"ltr\">[K in keyof T]</code> هكذا: “لكل مفتاح <code dir=\"ltr\">K</code> من <code dir=\"ltr\">T</code>”. يُقيَّم الطرف الأيمن مرة لكل مفتاح، مع ربط <code dir=\"ltr\">K</code> بذلك المفتاح، فيكون <code dir=\"ltr\">T[K]</code> نوع تلك الخاصية. أضف <code dir=\"ltr\">phone</code> إلى <code dir=\"ltr\">Address</code> فيكتسب <code dir=\"ltr\">AddressForm</code> مدخلًا لـ <code dir=\"ltr\">phone</code>، دون أي تعديل.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-at-3-2-t1\"><defs><marker id=\"arrow-l-at-3-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-at-3-2-t1\">مفاتيح Address، وهي line1 وcity وcountry وpostcode، يمرّ كلٌّ منها عبر جسم الـ mapped type، الذي يغلّف نوع الخاصية في كائن فيه value وerror وtouched، فينتج خاصية واحدة لكل مفتاح في FieldState الخاص بـ Address.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"20\" width=\"170\" height=\"190\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"95\" y=\"46\" text-anchor=\"middle\">keyof Address</text>\n  <text class=\"d-code\" x=\"95\" y=\"84\" text-anchor=\"middle\">'line1'</text>\n  <text class=\"d-code\" x=\"95\" y=\"114\" text-anchor=\"middle\">'city'</text>\n  <text class=\"d-code\" x=\"95\" y=\"144\" text-anchor=\"middle\">'country'</text>\n  <text class=\"d-code\" x=\"95\" y=\"174\" text-anchor=\"middle\">'postcode'</text>\n  <path class=\"d-arrow\" d=\"M180 115 L238 115\" marker-end=\"url(#arrow-l-at-3-2-1)\"/>\n  <rect class=\"d-box-primary\" x=\"242\" y=\"70\" width=\"210\" height=\"90\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"347\" y=\"100\" text-anchor=\"middle\">[K in keyof T]:</text>\n  <text class=\"d-code\" x=\"347\" y=\"126\" text-anchor=\"middle\">{ value: T[K];</text>\n  <text class=\"d-code\" x=\"347\" y=\"148\" text-anchor=\"middle\">error; touched }</text>\n  <path class=\"d-arrow\" d=\"M452 115 L506 115\" marker-end=\"url(#arrow-l-at-3-2-1)\"/>\n  <rect class=\"d-box-success\" x=\"510\" y=\"20\" width=\"180\" height=\"190\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"600\" y=\"46\" text-anchor=\"middle\">FieldState</text>\n  <text class=\"d-code\" x=\"600\" y=\"84\" text-anchor=\"middle\">line1: {…}</text>\n  <text class=\"d-code\" x=\"600\" y=\"114\" text-anchor=\"middle\">city: {…}</text>\n  <text class=\"d-code\" x=\"600\" y=\"144\" text-anchor=\"middle\">country: {…}</text>\n  <text class=\"d-code\" x=\"600\" y=\"174\" text-anchor=\"middle\">postcode?: {…}</text>\n</svg><figcaption>الـ mapped type يُنفّذ جسمه مرة لكل مفتاح</figcaption></figure>\n<p>لاحظ أن <code dir=\"ltr\">postcode</code> بقيت اختيارية. حين يمرّ الـ mapped type على <code dir=\"ltr\">keyof T</code> مباشرة، يعامله TypeScript على أنه <strong>تماثليّ الشكل (homomorphic)</strong>: ينسخ معدِّلَي <code dir=\"ltr\">?</code> و<code dir=\"ltr\">readonly</code> لكل خاصية من <code dir=\"ltr\">T</code>. وهذا ما تريده عادة، وهو سبب احتفاظ الـ utility types المدمجة بالاختيارية.</p>\n<p>لست مضطرًا إلى المرور على <code dir=\"ltr\">keyof</code>. أي union من المفاتيح يعمل، وهذا كل ما في <code dir=\"ltr\">Record</code>:</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\"> MyRecord</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PropertyKey</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\">P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</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\"> V</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\"> CountsByStatus</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyRecord</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'shipped'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'delivered'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span></code></pre></div>\n<h2 id=\"sec-2\" tabindex=\"-1\">إضافة المعدِّلات وإزالتها</h2>\n<p>ضع <code dir=\"ltr\">?</code> أو <code dir=\"ltr\">readonly</code> أمام الخاصية لإضافتها، وابدأ بـ <code dir=\"ltr\">-</code> لإزالتها. بهذا تصبح معظم الـ utility types في الـ 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\"> MyPartial</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\">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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyReadonly</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:#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\"> 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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyRequired</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\">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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Mutable</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:#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\"> 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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyPick</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:#CF222E;--shiki-dark:#FF7B72\"> extends</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:#1F2328;--shiki-dark:#E6EDF3\"> { [</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</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 style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] };</span></span></code></pre></div>\n<p><code dir=\"ltr\">MyPartial&lt;Address&gt;</code> هو نوع جسم طلب PATCH. و<code dir=\"ltr\">MyReadonly&lt;Address&gt;</code> هو النسخة المجمّدة لشاشة المراجعة. و<code dir=\"ltr\">-?</code> تفعل شيئًا إضافيًا قد لا تتوقّعه: تُزيل أيضًا الـ <code dir=\"ltr\">undefined</code> الذي تحمله الخاصية الاختيارية ضمنيًا، فيكون <code dir=\"ltr\">MyRequired&lt;Address&gt;['postcode']</code> هو <code dir=\"ltr\">string</code>، لا <code dir=\"ltr\">string | undefined</code>.</p>\n<p><code dir=\"ltr\">MyPick</code> يستحق نظرة ثانية. إنه يمرّ على <code dir=\"ltr\">K</code> لا على <code dir=\"ltr\">keyof T</code>، لكن لأن <code dir=\"ltr\">K</code> مقيَّد بـ <code dir=\"ltr\">keyof T</code>، يظل TypeScript يعامله كتماثليّ الشكل ويحتفظ بمعدِّلات الخصائص المختارة.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>توقّع تحويلات عميقة</span></p>\n<p><code dir=\"ltr\">Readonly&lt;Order&gt;</code> تجعل <code dir=\"ltr\">order.lines = []</code> خطأً لكنها تسمح بسرور بـ <code dir=\"ltr\">order.lines[0].sku = 'x'</code>. كل mapped type مدمج <strong>سطحي</strong>: يحوّل الخصائص في المستوى الأعلى ويترك الكائنات المتداخلة كما كانت تمامًا. إن احتجت إلى نسخة عميقة، فهذا يتطلّب التعاود (recursion)، وهو موضوع <a href=\"/ar/lesson/advanced-typescript/l-at-3-5\">الأنواع التعاودية ومتى تتوقّف</a>، مع الأسباب التي قد تجعلك لا تريد واحدة.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">إعادة تسمية المفاتيح بـ as</h2>\n<p>منذ TypeScript 4.1، يستطيع الـ mapped type إعادة تسمية كل مفتاح بجملة <code dir=\"ltr\">as</code>. يمكن أن يكون الاسم الجديد أي تعبير نوعي يتضمّن <code dir=\"ltr\">K</code>، وعادةً ما يكون template literal:</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\"> Setters</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\"> as</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `set${</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Capitalize</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#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:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> AddressSetters</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Setters</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Address</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { setLine1: (value: string) => void; setCity: …; setCountry: …; setPostcode?: … }</span></span></code></pre></div>\n<p><code dir=\"ltr\">K &amp; string</code> ضرورية لأن <code dir=\"ltr\">keyof T</code> قد يتضمّن مفاتيح <code dir=\"ltr\">number</code> و<code dir=\"ltr\">symbol</code>، و<code dir=\"ltr\">Capitalize</code> لا تعمل إلا على النصوص. سترى الـ template literal types كما ينبغي في <a href=\"/ar/lesson/advanced-typescript/l-at-3-4\">الـ Template Literal Types</a>.</p>\n<p>ولجملة <code dir=\"ltr\">as</code> قوة ثانية: <strong>المفتاح المحوَّل إلى <code dir=\"ltr\">never</code> يُحذف</strong>. وهكذا بالضبط يُعرَّف <code dir=\"ltr\">Omit</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\"> MyOmit</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:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PropertyKey</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\">P</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\"> as</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Exclude</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</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 style=\"color:#953800;--shiki-dark:#FFA657\">P</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\"> NewAddress</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyOmit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Address</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'postcode'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { line1; city; country }</span></span></code></pre></div>\n<p><code dir=\"ltr\">Exclude&lt;P, K&gt;</code> تعطي <code dir=\"ltr\">never</code> حين يكون <code dir=\"ltr\">P</code> أحد المفاتيح المحذوفة، و<code dir=\"ltr\">P</code> في غير ذلك. في الدرس القادم سترى كيف تعمل <code dir=\"ltr\">Exclude</code>، وكيف تصفّي المفاتيح حسب <em>نوع</em> قيمتها لا حسب اسمها.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">وضعها موضع التطبيق</h2>\n<p>أنواع مثل <code dir=\"ltr\">FieldState</code> تؤتي ثمارها في الدوال التي تستخدمها. إليك دالة تحديث النموذج، مكتوبة مرة واحدة لأي نموذج، لا للعناوين فقط:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"6\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> setField</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:#CF222E;--shiki-dark:#FF7B72\"> extends</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:#953800;--shiki-dark:#FFA657\">form</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FieldState</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\">key</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 style=\"color:#953800;--shiki-dark:#FFA657\">value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#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\"> FieldState</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">form, [key]: { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">form[key], value, touched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(addressForm, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'city'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dubai'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ok</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(addressForm, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'city'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Argument of type 'number' is not assignable to parameter of type 'string'.</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(addressForm, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cty'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dubai'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Argument of type '\"cty\"' is not assignable to parameter of type 'keyof Address'.</span></span></code></pre></div>\n<p>هذا نمط <code dir=\"ltr\">pluck</code> من القسم 2 مدموجًا مع mapped type. يُستنتج <code dir=\"ltr\">K</code> من المفتاح الذي تمرّره، وتفحص <code dir=\"ltr\">T[K]</code> القيمة مقابل نوع ذلك الحقل، ويضمن الـ mapped type أن للنموذج مدخلًا لكل حقل. وحين يُضاف حقل <code dir=\"ltr\">deliveryNotes</code> إلى <code dir=\"ltr\">Address</code>، يقبله نوع النموذج والـ setters وهذه الدالة فورًا، ويُعلَّم كل مكان يبني نموذجًا كاملًا إلى أن يتعامل مع الحقل الجديد.</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>سمِّ التحويلات التي تعيد استخدامها</span></p>\n<p>الـ mapped types المكتوبة مباشرة داخل توقيعات الدوال صعبة القراءة. أعطِ كل تحويل اسمًا يقول ما الغاية منه (<code dir=\"ltr\">FieldState</code> و<code dir=\"ltr\">Patch</code> و<code dir=\"ltr\">Setters</code>)، وأبقِه بجوار الأنواع التي يخدمها، ودع تلميحات التمرير تعرض التوسيع. يجب أن يفهم القارئ التوقيع من الاسم وحده.</p>\n</aside>\n<p>في التمرين ستعيد بناء أربعة utility types وتبني أنواع نموذج العنوان منها. والدرس القادم يضيف القطعة الناقصة، الشروط، كي تستطيع تحويلاتك اتخاذ القرارات.</p>\n","toc":[{"id":"sec-1","text":"الحلقة","level":2},{"id":"sec-2","text":"إضافة المعدِّلات وإزالتها","level":2},{"id":"sec-3","text":"إعادة تسمية المفاتيح بـ as","level":2},{"id":"sec-4","text":"وضعها موضع التطبيق","level":2}],"takeaways":["الـ mapped type بالشكل <code dir=\"ltr\">{ [K in Keys]: … }</code> يمرّ على union من المفاتيح ويبني خاصية واحدة لكل مفتاح.","المرور على <code dir=\"ltr\">keyof T</code> تماثليّ الشكل (homomorphic)، فتُنسخ معدِّلات الاختيارية وreadonly من <code dir=\"ltr\">T</code> ما لم تُضفها أو تُزلها أنت.","<code dir=\"ltr\">+?</code> و<code dir=\"ltr\">-?</code> و<code dir=\"ltr\">+readonly</code> و<code dir=\"ltr\">-readonly</code> تضيف المعدِّلات أو تنزعها؛ و<code dir=\"ltr\">Required&lt;T&gt;</code> ليس إلا <code dir=\"ltr\">{ [K in keyof T]-?: T[K] }</code>.","جملة <code dir=\"ltr\">as</code> تعيد تسمية كل مفتاح، وتحويل مفتاح إلى <code dir=\"ltr\">never</code> يحذفه، وهكذا يعمل <code dir=\"ltr\">Omit</code>.","الـ mapped types المدمجة سطحية؛ الكائنات المتداخلة تحتفظ بمعدِّلاتها الأصلية."],"further":[{"title":"Mapped Types","url":"https://www.typescriptlang.org/docs/handbook/2/mapped-types.html"},{"title":"Utility Types","url":"https://www.typescriptlang.org/docs/handbook/utility-types.html"}],"quiz":[{"q":"<p>ما هو <code dir=\"ltr\">Draft</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\"> Address</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:#953800;--shiki-dark:#FFA657\"> line1</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\">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\">postcode</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Draft</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\"> Address</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-?:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Address</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></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">{ line1: string; city: string; postcode: string }</code>","why":"صحيح. <code dir=\"ltr\">-readonly</code> تنزع readonly عن <code dir=\"ltr\">line1</code>، و<code dir=\"ltr\">-?</code> تجعل <code dir=\"ltr\">postcode</code> إلزامية، ويُزال أيضًا الـ <code dir=\"ltr\">undefined</code> الذي جاء مع <code dir=\"ltr\">?</code>."},{"text":"<code dir=\"ltr\">{ readonly line1: string; city: string; postcode?: string }</code>","why":"هذا ما ستحصل عليه دون معدِّلات <code dir=\"ltr\">-</code>؛ فالمرور التماثلي ينسخها افتراضيًا."},{"text":"<code dir=\"ltr\">{ line1: string; city: string; postcode: string | undefined }</code>","why":"<code dir=\"ltr\">-?</code> تُزيل أيضًا الـ <code dir=\"ltr\">undefined</code> الذي أضافه معدِّل الاختيارية، فيكون <code dir=\"ltr\">postcode</code> من نوع <code dir=\"ltr\">string</code> عادي."},{"text":"خطأ، لأن <code dir=\"ltr\">-?</code> غير مسموحة إلا في <code dir=\"ltr\">Required</code>.","why":"<code dir=\"ltr\">Required</code> مجرّد mapped type عادي في الـ lib؛ والمعدِّل <code dir=\"ltr\">-?</code> يعمل في أي mapped type."}],"answer":0},{"q":"<p>أيّ تعريف يعيد بناء <code dir=\"ltr\">Omit&lt;T, K&gt;</code>؟</p>","options":[{"text":"<code dir=\"ltr\">{ [P in keyof T]: P extends K ? never : T[P] }</code>","why":"هذا يُبقي كل المفاتيح ويجعل نوع المحذوفة منها <code dir=\"ltr\">never</code>، فتبقى خصائص إلزامية لا يمكن لشيء أن يحقّقها."},{"text":"<code dir=\"ltr\">{ [P in K]: T[P] }</code>","why":"هذا لا يُبقي إلا المفاتيح المدرَجة، وهذا هو <code dir=\"ltr\">Pick</code>، بل إنه لا يمرّ في الترجمة دون <code dir=\"ltr\">K extends keyof T</code>."},{"text":"<code dir=\"ltr\">{ [P in keyof T as Exclude&lt;P, K&gt;]: T[P] }</code>","why":"صحيح. جملة <code dir=\"ltr\">as</code> تحوّل المفاتيح المحذوفة إلى <code dir=\"ltr\">never</code>، والمفتاح المحوَّل إلى <code dir=\"ltr\">never</code> يُحذف من النتيجة."}],"answer":2},{"q":"<p><code dir=\"ltr\">type Setters&lt;T&gt; = { [K in keyof T as </code>set${Capitalize<K>}<code dir=\"ltr\">]: (v: T[K]) =&gt; void }</code> لا يمرّ في الترجمة. لماذا؟</p>","options":[{"text":"الـ template literal types غير مسموحة في جمل <code dir=\"ltr\">as</code>.","why":"إنها مسموحة؛ إعادة تسمية المفاتيح بالـ template literals هي الاستخدام الرئيسي لجمل <code dir=\"ltr\">as</code>."},{"text":"<code dir=\"ltr\">keyof T</code> قد يتضمّن مفاتيح <code dir=\"ltr\">number</code> و<code dir=\"ltr\">symbol</code>، و<code dir=\"ltr\">Capitalize</code> لا تقبل إلا النصوص، فاكتب <code dir=\"ltr\">Capitalize&lt;K &amp; string&gt;</code>.","why":"صحيح. التقاطع مع <code dir=\"ltr\">string</code> لا يُبقي إلا المفاتيح النصية، التي تستطيع <code dir=\"ltr\">Capitalize</code> التعامل معها."},{"text":"<code dir=\"ltr\">T[K]</code> غير مسموحة بعد إعادة تسمية المفتاح.","why":"داخل الـ mapped type، يبقى <code dir=\"ltr\">K</code> هو المفتاح الأصلي، فتعمل <code dir=\"ltr\">T[K]</code> كالمعتاد."}],"answer":1},{"q":"<p>طُبّق <code dir=\"ltr\">Readonly&lt;Order&gt;</code> على <code dir=\"ltr\">type Order = { id: number; lines: { sku: string }[] }</code>. أيّ إسناد ما زال مسموحًا؟</p>","options":[{"text":"<code dir=\"ltr\">order.id = 1043</code>","why":"<code dir=\"ltr\">id</code> خاصية في المستوى الأعلى، و<code dir=\"ltr\">Readonly</code> تجعل كل خاصية في المستوى الأعلى للقراءة فقط."},{"text":"<code dir=\"ltr\">order.lines = []</code>","why":"<code dir=\"ltr\">lines</code> نفسها خاصية في المستوى الأعلى، فإعادة الإسناد إليها مرفوضة."},{"text":"<code dir=\"ltr\">order.lines[0].sku = 'TENT-3P'</code>","why":"صحيح. <code dir=\"ltr\">Readonly</code> سطحية؛ المصفوفة وكائناتها تحتفظ بأنواعها الأصلية القابلة للتعديل."}],"answer":2}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"type Address = { line1: string; city: string; country: string; postcode?: string };\n\n// Re-implement these with mapped types (do not use the built-in utility types).\ntype MyPartial<T> = T;\ntype MyRequired<T> = T;\ntype MyPick<T, K extends keyof T> = T;\ntype MyOmit<T, K extends PropertyKey> = T;\n\n// For each property: its value, a validation error and whether it was touched.\ntype FieldState<T> = any;\n\n// For each property `name`, a `setName(value)` function.\ntype Setters<T> = any;\n\nfunction initialState<T extends object>(values: T): FieldState<T> {\n  return Object.fromEntries(\n    Object.entries(values).map(([key, value]) => [key, { value, error: null, touched: false }]),\n  );\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<MyPartial<Address>, { line1?: string; city?: string; country?: string; postcode?: string }>>;\ntype T2 = Expect<Equal<MyRequired<Address>, { line1: string; city: string; country: string; postcode: string }>>;\ntype T3 = Expect<Equal<MyPick<Address, 'city' | 'postcode'>, { city: string; postcode?: string }>>;\ntype T4 = Expect<Equal<MyOmit<Address, 'postcode' | 'country'>, { line1: string; city: string }>>;\ntype T5 = Expect<Equal<FieldState<{ city: string; floor: number }>, {\n  city: { value: string; error: string | null; touched: boolean };\n  floor: { value: number; error: string | null; touched: boolean };\n}>>;\ntype T6 = Expect<Equal<Setters<{ city: string; floor: number }>, {\n  setCity: (value: string) => void;\n  setFloor: (value: number) => void;\n}>>;\n\nconst form = initialState({ line1: '12 Nile St', city: 'Cairo', country: 'EG' });\nconsole.log(form.city);\n"},"solution":{"ts":"type Address = { line1: string; city: string; country: string; postcode?: string };\n\ntype MyPartial<T> = { [K in keyof T]?: T[K] };\ntype MyRequired<T> = { [K in keyof T]-?: T[K] };\ntype MyPick<T, K extends keyof T> = { [P in K]: T[P] };\ntype MyOmit<T, K extends PropertyKey> = { [P in keyof T as Exclude<P, K>]: T[P] };\n\ntype FieldState<T> = {\n  [K in keyof T]: { value: T[K]; error: string | null; touched: boolean };\n};\n\ntype Setters<T> = {\n  [K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void;\n};\n\nfunction initialState<T extends object>(values: T): FieldState<T> {\n  // Object.fromEntries cannot know the keys, so we cast once here.\n  return Object.fromEntries(\n    Object.entries(values).map(([key, value]) => [key, { value, error: null, touched: false }]),\n  ) as FieldState<T>;\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<MyPartial<Address>, { line1?: string; city?: string; country?: string; postcode?: string }>>;\ntype T2 = Expect<Equal<MyRequired<Address>, { line1: string; city: string; country: string; postcode: string }>>;\ntype T3 = Expect<Equal<MyPick<Address, 'city' | 'postcode'>, { city: string; postcode?: string }>>;\ntype T4 = Expect<Equal<MyOmit<Address, 'postcode' | 'country'>, { line1: string; city: string }>>;\ntype T5 = Expect<Equal<FieldState<{ city: string; floor: number }>, {\n  city: { value: string; error: string | null; touched: boolean };\n  floor: { value: number; error: string | null; touched: boolean };\n}>>;\ntype T6 = Expect<Equal<Setters<{ city: string; floor: number }>, {\n  setCity: (value: string) => void;\n  setFloor: (value: number) => void;\n}>>;\n\nconst form = initialState({ line1: '12 Nile St', city: 'Cairo', country: 'EG' });\nconsole.log(form.city);\n"},"checks":[{"id":"types","typecheck":true},{"id":"runtime","test":"const f = initialState({ city: 'Amman', floor: 3 }); return f.city.value === 'Amman' && f.floor.touched === false && f.floor.error === null;"}],"title":"أعِد بناء الـ utility types","prompt":"<p>باستخدام الـ mapped types وحدها (بلا <code dir=\"ltr\">Partial</code> أو <code dir=\"ltr\">Required</code> أو <code dir=\"ltr\">Pick</code> أو <code dir=\"ltr\">Omit</code> أو <code dir=\"ltr\">Record</code> المدمجة)، نفّذ:</p>\n<ul>\n<li><code dir=\"ltr\">MyPartial&lt;T&gt;</code> و<code dir=\"ltr\">MyRequired&lt;T&gt;</code> و<code dir=\"ltr\">MyPick&lt;T, K&gt;</code> و<code dir=\"ltr\">MyOmit&lt;T, K&gt;</code>، بسلوك يطابق المدمجة.</li>\n<li><code dir=\"ltr\">FieldState&lt;T&gt;</code>: لكل خاصية، <code dir=\"ltr\">{ value: T[K]; error: string | null; touched: boolean }</code>.</li>\n<li><code dir=\"ltr\">Setters&lt;T&gt;</code>: لكل خاصية <code dir=\"ltr\">name</code>، خاصية <code dir=\"ltr\">setName</code> هي دالة تأخذ نوع تلك الخاصية وتُعيد <code dir=\"ltr\">void</code>.</li>\n</ul>\n<p>ثم اجعل <code dir=\"ltr\">initialState</code> تمرّ في الترجمة بنوع إرجاعها الجديد.</p>\n","hints":["الحلقة الأساسية هي <code dir=\"ltr\">{ [K in keyof T]: … }</code>. أضف <code dir=\"ltr\">?</code> بعد المفتاح للاختيارية، و<code dir=\"ltr\">-?</code> لإزالتها.","في <code dir=\"ltr\">MyOmit</code>، أعِد تسمية المفاتيح بـ <code dir=\"ltr\">as</code> وأرسل المحذوفة منها إلى <code dir=\"ltr\">never</code>: <code dir=\"ltr\">as Exclude&lt;P, K&gt;</code>. استخدام <code dir=\"ltr\">Exclude</code> المدمجة مسموح.","في <code dir=\"ltr\">Setters</code>، أعِد التسمية بـ template literal: <code dir=\"ltr\">as `set${Capitalize&lt;K &amp; string&gt;}` </code>. وفي <code dir=\"ltr\">initialState</code>، تُعيد <code dir=\"ltr\">Object.fromEntries</code> النوع <code dir=\"ltr\">{ [k: string]: … }</code>، فحوّل نتيجتها مرة واحدة إلى <code dir=\"ltr\">FieldState&lt;T&gt;</code>."],"explanation":"<p>كل utility type حلقة تكرار على المفاتيح. المرور على <code dir=\"ltr\">keyof T</code> ينسخ المعدِّلات، فتضيف <code dir=\"ltr\">MyPartial</code> الرمز <code dir=\"ltr\">?</code>، وتُزيله <code dir=\"ltr\">MyRequired</code> بـ <code dir=\"ltr\">-?</code> (التي تُسقط أيضًا الـ <code dir=\"ltr\">undefined</code> الضمني)، وتحتفظ <code dir=\"ltr\">MyPick</code> بالمعدِّلات الأصلية لأن <code dir=\"ltr\">K</code> مقيَّد بـ <code dir=\"ltr\">keyof T</code>. وتعمل <code dir=\"ltr\">MyOmit</code> لأن المفتاح المحوَّل إلى <code dir=\"ltr\">never</code> يختفي. أما <code dir=\"ltr\">Setters</code> فتُظهر الاستخدام الآخر لـ <code dir=\"ltr\">as</code>: إعادة تسمية كل مفتاح بـ template literal، حيث تصفّي <code dir=\"ltr\">K &amp; string</code> مفاتيح <code dir=\"ltr\">number</code> و<code dir=\"ltr\">symbol</code> التي لا تستطيع <code dir=\"ltr\">Capitalize</code> التعامل معها. ولا تستطيع <code dir=\"ltr\">Object.fromEntries</code> معرفة مفاتيحك، فيمنح تحويلٌ واحد موثّق داخل <code dir=\"ltr\">initialState</code> المستدعين نوعًا دقيقًا.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ، بما فيها كل اختبارات الأنواع","runtime":"<code dir=\"ltr\">initialState</code> تبني لكل حقل حالة غير ملموسة وخالية من الأخطاء"}},"words":615,"readMinutes":4}