{"id":"l-at-2-1","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"اكتب دوالًا generic تربط معاملاتُ النوع فيها المدخلات بالمخرجات، وقيّدها بـ extends، وتعرّف على الـ generics التي هي في الحقيقة تحويلات غير مفحوصة.","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\"> DetailedOrder</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\">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 style=\"color:#953800;--shiki-dark:#FFA657\">customer</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</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:#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\">orders].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.totalCents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.totalCents);</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\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> DetailedOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sorted</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sorted[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].customer;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Property 'customer' does not exist on type '{ totalCents: number; }'.</span></span></code></pre></div>\n<p>الكائنات في <code dir=\"ltr\">sorted</code> ما زالت طلبات كاملة وقت التشغيل. لكن توقيع الدالة لا يستطيع وصف مخرجها إلا من خلال التعليق النوعي على مدخلها، وهذا التعليق لا يذكر إلا <code dir=\"ltr\">totalCents</code>. والإصلاح ليس تعليقًا أوسع، بل <strong>علاقة</strong>: “أيًّا كان نوع الكائن الذي تعطيني إياه، ستحصل على النوع نفسه”.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">معامل النوع علاقة</h2>\n<p>هذه العلاقة هي ما يقوله الـ generic:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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:#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 style=\"color:#953800;--shiki-dark:#FFA657\">orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[])</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#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\">orders].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.totalCents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.totalCents);</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sorted</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// DetailedOrder[]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sorted[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].customer;                 </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// string</span></span></code></pre></div>\n<p>اقرأ التوقيع بصوت عالٍ: “لنوعٍ ما <code dir=\"ltr\">T</code> فيه <code dir=\"ltr\">totalCents</code> رقمية، خذ مصفوفة من <code dir=\"ltr\">T</code> وأعِد مصفوفة من <code dir=\"ltr\">T</code>”. المستدعي لا يكتب <code dir=\"ltr\">&lt;DetailedOrder&gt;</code>؛ يستنتج TypeScript قيمة <code dir=\"ltr\">T</code> من الوسيط (والدرس القادم يشرح كيف بالضبط).</p>\n<p>جزءان يقومان بالعمل. <code dir=\"ltr\">T</code> يظهر في المعامل وفي نوع الإرجاع، فيربط بينهما. و<code dir=\"ltr\">extends { totalCents: number }</code> هو <strong>constraint (قيد)</strong>: يحدّ من الأنواع التي يمكن أن يكونها <code dir=\"ltr\">T</code>، وهذا ما يجعل <code dir=\"ltr\">a.totalCents</code> مشروعة داخل الدالة. دونه قد يكون <code dir=\"ltr\">T</code> أي شيء، وسيرفض TypeScript الوصول إلى الخاصية.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">قاعدة الظهور مرتين</h2>\n<p>أنفع اختبار للـ generic، وهو ما أطبّقه في كل مراجعة كود: <strong>يجب أن يظهر كل معامل نوع مرتين على الأقل</strong>. مرة ليلتقط نوعًا، ومرة ليستخدمه في مكان آخر.</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> lastItem</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\">items</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[])</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// input → output</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[items.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> indexById</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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:#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\">items</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[])</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">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:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(items.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">item</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [item.id, item]));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> merge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">A</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">B</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> A</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> B</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> A</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> B</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {              </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// two inputs → output</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\">a, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">b };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>المعامل الذي يظهر مرة واحدة لا يربط شيئًا. <code dir=\"ltr\">function log&lt;T&gt;(value: T): void</code> هي ببساطة <code dir=\"ltr\">function log(value: unknown): void</code> مع صياغة زائدة، و<code dir=\"ltr\">function count&lt;T&gt;(items: T[]): number</code> هي <code dir=\"ltr\">items: unknown[]</code>. احذف معاملات النوع هذه؛ تصبح التوقيعات أسهل قراءة ولا تخسر شيئًا.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الـ generics كتحويلات</span></p>\n<p><code dir=\"ltr\">function parseJson&lt;T&gt;(text: string): T { return JSON.parse(text); }</code> تبدو آمنة من حيث النوع، وكل موضع استدعاء يُقرأ بلطف: <code dir=\"ltr\">parseJson&lt;Order&gt;(body)</code>. لكن <code dir=\"ltr\">T</code> لا يظهر إلا في نوع الإرجاع، فيستطيع المستدعي اختيار أي نوع ولا شيء يفحصه. إنها <code dir=\"ltr\">JSON.parse(body) as Order</code> متنكّرة، وتُخفي التحويل حيث لن يراه المراجعون. أعِد <code dir=\"ltr\">unknown</code> وتحقّق (انظر <a href=\"/ar/lesson/advanced-typescript/l-at-4-3\">تحليل البيانات غير الموثوقة عند الحدود</a>)؛ وحين يتحتّم عليك التحويل فعلًا، اكتب <code dir=\"ltr\">as</code> في موضع الاستدعاء حيث يكون مرئيًا.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">المستدعي هو من يختار T</h2>\n<p>هذا الخطأ يربك الجميع تقريبًا في المرة الأولى:</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\"> Shippable</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">weightGrams</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> withDefaultWeight</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\"> Shippable</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\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { weightGrams: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // Error: Type '{ weightGrams: number; }' is not assignable to type 'T'.</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  //   '{ weightGrams: number; }' is assignable to the constraint of type 'T', but 'T'</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  //   could be instantiated with a different subtype of constraint 'Shippable'.</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>القيد يقول إن <code dir=\"ltr\">T</code> <em>على الأقل</em> <code dir=\"ltr\">Shippable</code>. قد يطلب المستدعي <code dir=\"ltr\">withDefaultWeight&lt;{ weightGrams: number; sku: string }&gt;()</code>، وقيمتك الحرفية ليس فيها <code dir=\"ltr\">sku</code>. داخل الدالة الـ generic، <code dir=\"ltr\">T</code> نوعٌ لا تختاره أنت؛ لا يمكنك إنتاج <code dir=\"ltr\">T</code> إلا من قيم هي <code dir=\"ltr\">T</code> أصلًا، مثل الوسائط. وإن كانت دالتك تبني القيمة بنفسها، فنوع إرجاعها هو القيد (<code dir=\"ltr\">Shippable</code>)، ولا حاجة إلى generic.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">قيود تستخدم معاملات أخرى</h2>\n<p>يمكن أن يشير القيد إلى معامل نوع آخر. المثال الكلاسيكي يختار خاصية باسمها:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> pluck</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\">items</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\">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:#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\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">item</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> item[key]);</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> customers</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> pluck</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'customer'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// string[]</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">pluck</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'email'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Argument of type '\"email\"' is not assignable to parameter of type 'keyof DetailedOrder'.</span></span></code></pre></div>\n<p><code dir=\"ltr\">keyof T</code> هو الـ union من أسماء خصائص <code dir=\"ltr\">T</code>، و<code dir=\"ltr\">T[K]</code> هو نوع الخاصية <code dir=\"ltr\">K</code>. ستفكّك الاثنين في القسم 3؛ أما الآن فلاحظ النمط. <code dir=\"ltr\">K</code> يظهر في المعامل وفي نوع الإرجاع، وقيده يربطه بـ <code dir=\"ltr\">T</code>. كل معامل يظهر مرتين على الأقل، وكلٌّ منهما يستحق مكانه.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">generic أم union بسيط؟</h2>\n<p>ليست كل دالة تقبل عدة أنواع بحاجة إلى معامل نوع. اسأل سؤالًا واحدًا: <strong>هل يعتمد نوع المخرج على نوع المدخل الذي مرّره المستدعي؟</strong></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:#6E7781;--shiki-dark:#8B949E\">// Output is always a number, whatever came in: a union is enough.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> toCents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">amount</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\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">Number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(amount) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</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:#6E7781;--shiki-dark:#8B949E\">// Output depends on the input: a generic says so.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> firstOrThrow</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\">items</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[])</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#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\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (items.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ===</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Expected at least one item'</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\"> items[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">toCents</code> تُعيد <code dir=\"ltr\">number</code> مهما كان المدخل، فجعلها <code dir=\"ltr\">toCents&lt;T extends number | string&gt;(amount: T): number</code> سيضيف معاملًا يظهر مرة واحدة ولا يربط شيئًا. و<code dir=\"ltr\">firstOrThrow</code> عكسها: قائمة طلبات يجب أن تُعيد طلبًا، وقائمة منتجات تُعيد منتجًا. أما توقيع الـ union (<code dir=\"ltr\">items: unknown[]): unknown</code>) فسيُجبر كل مستدعٍ على التحويل.</p>\n<p>حين لا تكون متأكّدًا، ابدأ دون الـ generic. إذا بدأ المستدعون يكتبون <code dir=\"ltr\">as</code> على النتيجة، فتلك إشارة إلى أن المخرج يعتمد على المدخل، وأن معامل نوع سيُزيل تلك التحويلات.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الأنواع الـ generic تتبع القاعدة نفسها</span></p>\n<p>الأنواع تأخذ معاملات أيضًا: <code dir=\"ltr\">type Page&lt;T&gt; = { items: T[]; nextCursor: string | null }</code>. يُعيد عميل Cartwheel <code dir=\"ltr\">Page&lt;DetailedOrder&gt;</code> و<code dir=\"ltr\">Page&lt;Product&gt;</code> من كود الترقيم نفسه. والقاعدة ما زالت قائمة: معامل النوع غير المستخدَم في جسم النوع علامة على أن النوع يفعل أقل مما يدّعي.</p>\n</aside>\n<p>في التمرين ستحوّل أربع دوال مساعدة تُضيّع المعلومات إلى دوال generic تحتفظ بأنواع المستدعي. والدرس القادم ينظر في كيف يقرّر TypeScript ما هو <code dir=\"ltr\">T</code> حين لا يقوله المستدعي.</p>\n","toc":[{"id":"sec-1","text":"معامل النوع علاقة","level":2},{"id":"sec-2","text":"قاعدة الظهور مرتين","level":2},{"id":"sec-3","text":"المستدعي هو من يختار T","level":2},{"id":"sec-4","text":"قيود تستخدم معاملات أخرى","level":2},{"id":"sec-5","text":"generic أم union بسيط؟","level":2}],"takeaways":["يستحق معامل النوع مكانه حين يظهر مرتين على الأقل، رابطًا مدخلًا بمخرج أو مدخلًا بمدخل آخر.","<code dir=\"ltr\">T extends { totalCents: number }</code> تتيح للدالة استخدام <code dir=\"ltr\">totalCents</code> مع إعادة النوع الكامل والأكثر تحديدًا الذي مرّره المستدعي.","الـ generic الذي لا يظهر إلا في نوع الإرجاع، مثل <code dir=\"ltr\">parse&lt;T&gt;(text): T</code>، هو تحويل غير مفحوص بصياغة أجمل.","المستدعي هو من يختار <code dir=\"ltr\">T</code> لا الدالة، ولهذا لا يمكنك إعادة كائن حرفي حيث يُنتظر <code dir=\"ltr\">T</code>."],"further":[{"title":"Generics","url":"https://www.typescriptlang.org/docs/handbook/2/generics.html"},{"title":"More on Functions (generic functions and constraints)","url":"https://www.typescriptlang.org/docs/handbook/2/functions.html#generic-functions"}],"quiz":[{"q":"<p>ما نوع <code dir=\"ltr\">sorted</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\"> DetailedOrder</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\">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 style=\"color:#953800;--shiki-dark:#FFA657\">customer</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\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> DetailedOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">xs</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</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 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\">xs].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.totalCents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.totalCents); }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sorted</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders);</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">DetailedOrder[]</code>، لأن المصفوفة تحتوي قيمًا من نوع <code dir=\"ltr\">DetailedOrder</code>","why":"القيم ما زالت طلبات كاملة وقت التشغيل، لكن نوع الإرجاع يُحسب من نوع المعامل، الذي لا يعرف إلا <code dir=\"ltr\">totalCents</code>."},{"text":"<code dir=\"ltr\">{ totalCents: number }[]</code>، فيكون <code dir=\"ltr\">sorted[0].customer</code> خطأً","why":"صحيح. دون معامل نوع، لا تملك الدالة طريقة لتقول “أُعيد لك النوع نفسه الذي أعطيتني إياه”."},{"text":"<code dir=\"ltr\">any[]</code>، لأن <code dir=\"ltr\">sort</code> تُعيد <code dir=\"ltr\">any</code>","why":"<code dir=\"ltr\">sort</code> تُعيد نوع عناصر المصفوفة نفسها. وفقدان المعلومات سببه التعليق النوعي على المعامل."}],"answer":1},{"q":"<p>أيّ هذه التوقيعات يستخدم معامل النوع بطريقة تضيف أمانًا حقيقيًا في الأنواع؟</p>","options":[{"text":"<code dir=\"ltr\">function log&lt;T&gt;(value: T): void</code>","why":"<code dir=\"ltr\">T</code> يظهر مرة واحدة، فلا يربط شيئًا. <code dir=\"ltr\">value: unknown</code> تقول الشيء نفسه بصدق أكبر."},{"text":"<code dir=\"ltr\">function parse&lt;T&gt;(json: string): T</code>","why":"<code dir=\"ltr\">T</code> لا يظهر إلا في نوع الإرجاع، فيختار المستدعي أي نوع ولا شيء يفحصه. إنه تحويل متنكّر."},{"text":"<code dir=\"ltr\">function lastItem&lt;T&gt;(items: T[]): T | undefined</code>","why":"صحيح. <code dir=\"ltr\">T</code> يظهر في المدخل والمخرج رابطًا بينهما، فيحصل المستدعي بالضبط على نوع العنصر الذي مرّره."},{"text":"<code dir=\"ltr\">function count&lt;T&gt;(items: T[]): number</code>","why":"<code dir=\"ltr\">T</code> لا يظهر إلا مرة واحدة في موضع ذي أهمية؛ و<code dir=\"ltr\">items: unknown[]</code> تعمل بالطريقة نفسها تمامًا."}],"answer":2},{"q":"<p>لماذا يُعدّ هذا خطأً؟</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> emptyCart</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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:#953800;--shiki-dark:#FFA657\">items</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\"> 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\"> { items: [] };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n","options":[{"text":"لأن <code dir=\"ltr\">[]</code> تُستنتج على أنها <code dir=\"ltr\">never[]</code>.","why":"<code dir=\"ltr\">never[]</code> قابلة للإسناد إلى <code dir=\"ltr\">string[]</code>. المشكلة في العلاقة بين القيمة الحرفية و<code dir=\"ltr\">T</code>."},{"text":"لأن الدوال الـ generic لا يمكنها إعادة كائنات حرفية.","why":"يمكنها ذلك، حين يكون نوع القيمة الحرفية هو ما يقوله نوع الإرجاع. هنا نوع الإرجاع هو <code dir=\"ltr\">T</code> الخاص بالمستدعي، وأنت لا تعرفه."},{"text":"لأن المستدعي يختار <code dir=\"ltr\">T</code>، الذي قد يكون <code dir=\"ltr\">{ items: string[]; owner: string }</code>، وقيمتك الحرفية ليس فيها <code dir=\"ltr\">owner</code>.","why":"صحيح. يقول الخطأ إن <code dir=\"ltr\">T</code> قد يُجسَّد بنوع فرعي مختلف من القيد. أعِد نوع القيد بدلًا من ذلك."}],"answer":2}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"function sortByTotal(orders: { totalCents: number }[]): { totalCents: number }[] {\n  return [...orders].sort((a, b) => a.totalCents - b.totalCents);\n}\n\nfunction indexById(items: { id: number }[]): Map<number, { id: number }> {\n  return new Map(items.map((item) => [item.id, item]));\n}\n\nfunction pluck(items: any[], key: string): any[] {\n  return items.map((item) => item[key]);\n}\n\nfunction lastItem(items: unknown[]): unknown {\n  return items[items.length - 1];\n}\n\n// ---- Type tests (do not edit) ----\ntype DetailedOrder = { id: number; totalCents: number; customer: string };\nconst orders: DetailedOrder[] = [\n  { id: 1003, totalCents: 4500, customer: 'Mona' },\n  { id: 1001, totalCents: 18900, customer: 'Karim' },\n  { id: 1002, totalCents: 1200, customer: 'Lea' },\n];\n\nconst sorted = sortByTotal(orders);\nconst byId = indexById(orders);\nconst customers = pluck(orders, 'customer');\nconst last = lastItem(orders);\n\ntype T1 = Expect<Equal<typeof sorted, DetailedOrder[]>>;\ntype T2 = Expect<Equal<typeof byId, Map<number, DetailedOrder>>>;\ntype T3 = Expect<Equal<typeof customers, string[]>>;\ntype T4 = Expect<Equal<typeof last, DetailedOrder | undefined>>;\n\n// @ts-expect-error: products have no totalCents\nsortByTotal([{ sku: 'TENT-2P' }]);\n\n// @ts-expect-error: 'email' is not a property of DetailedOrder\npluck(orders, 'email');\n\nconsole.log(sorted.map((o) => o.customer), byId.get(1001)?.customer, customers, last?.id);\n"},"solution":{"ts":"function sortByTotal<T extends { totalCents: number }>(orders: T[]): T[] {\n  return [...orders].sort((a, b) => a.totalCents - b.totalCents);\n}\n\nfunction indexById<T extends { id: number }>(items: T[]): Map<number, T> {\n  return new Map(items.map((item) => [item.id, item]));\n}\n\nfunction pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {\n  return items.map((item) => item[key]);\n}\n\nfunction lastItem<T>(items: T[]): T | undefined {\n  return items[items.length - 1];\n}\n\n// ---- Type tests (do not edit) ----\ntype DetailedOrder = { id: number; totalCents: number; customer: string };\nconst orders: DetailedOrder[] = [\n  { id: 1003, totalCents: 4500, customer: 'Mona' },\n  { id: 1001, totalCents: 18900, customer: 'Karim' },\n  { id: 1002, totalCents: 1200, customer: 'Lea' },\n];\n\nconst sorted = sortByTotal(orders);\nconst byId = indexById(orders);\nconst customers = pluck(orders, 'customer');\nconst last = lastItem(orders);\n\ntype T1 = Expect<Equal<typeof sorted, DetailedOrder[]>>;\ntype T2 = Expect<Equal<typeof byId, Map<number, DetailedOrder>>>;\ntype T3 = Expect<Equal<typeof customers, string[]>>;\ntype T4 = Expect<Equal<typeof last, DetailedOrder | undefined>>;\n\n// @ts-expect-error: products have no totalCents\nsortByTotal([{ sku: 'TENT-2P' }]);\n\n// @ts-expect-error: 'email' is not a property of DetailedOrder\npluck(orders, 'email');\n\nconsole.log(sorted.map((o) => o.customer), byId.get(1001)?.customer, customers, last?.id);\n"},"checks":[{"id":"types","typecheck":true},{"id":"sorts-copy","test":"const xs = [{ totalCents: 3 }, { totalCents: 1 }, { totalCents: 2 }]; const out = sortByTotal(xs); return out.map((o) => o.totalCents).join() === '1,2,3' && xs[0].totalCents === 3;"},{"id":"indexes","test":"const m = indexById([{ id: 7, n: 'a' }, { id: 9, n: 'b' }]); return m.get(9).n === 'b' && m.size === 2;"},{"id":"plucks","test":"return pluck([{ a: 1 }, { a: 2 }], 'a').join() === '1,2' && lastItem([1, 2, 3]) === 3 && lastItem([]) === undefined;"}],"title":"اجعل الدوال المساعدة تحتفظ بأنواعها","prompt":"<p>هذه الدوال المساعدة الأربع من صفحة سجل الطلبات في Cartwheel تعمل وقت التشغيل لكنها تُضيّع معلومات الأنواع. اجعل كل واحدة منها generic حتى تنجح اختبارات الأنواع:</p>\n<ul>\n<li><code dir=\"ltr\">sortByTotal</code> تُعيد <strong>نسخة</strong> مرتّبة نوعها هو نوع عنصر المستدعي، وتبقى ترفض الكائنات التي ليس فيها <code dir=\"ltr\">totalCents</code>.</li>\n<li><code dir=\"ltr\">indexById</code> تُعيد <code dir=\"ltr\">Map</code> من المعرّف إلى نوع عنصر المستدعي.</li>\n<li><code dir=\"ltr\">pluck(items, key)</code> لا تقبل إلا أسماء خصائص حقيقية، وتُعيد مصفوفة من نوع تلك الخاصية.</li>\n<li><code dir=\"ltr\">lastItem</code> تُعيد العنصر الأخير، بنوع العنصر أو <code dir=\"ltr\">undefined</code>.</li>\n</ul>\n<p>أبقِ السلوك وقت التشغيل كما هو، ولا تستخدم <code dir=\"ltr\">any</code> أو <code dir=\"ltr\">as</code>.</p>\n","hints":["كل دالة تحتاج معامل نوع يظهر في المعامل وفي نوع الإرجاع معًا.","قيّد بـ <code dir=\"ltr\">extends</code>: <code dir=\"ltr\">&lt;T extends { totalCents: number }&gt;</code> تتيح لك قراءة <code dir=\"ltr\">totalCents</code> مع إعادة <code dir=\"ltr\">T[]</code>.","في <code dir=\"ltr\">pluck</code>، استخدم معاملَي نوع: <code dir=\"ltr\">&lt;T, K extends keyof T&gt;(items: T[], key: K): T[K][]</code>."],"explanation":"<p>صار كل معامل نوع يظهر مرتين على الأقل، رابطًا المدخل بالمخرج، فيحصل المستدعي بالضبط على نوع العنصر الذي مرّره. القيود (<code dir=\"ltr\">extends { totalCents: number }</code> و<code dir=\"ltr\">extends { id: number }</code>) هي ما يجعل قراءة الخصائص مشروعة داخل الدوال، وهي أيضًا ما يرفض <code dir=\"ltr\">[{ sku: 'TENT-2P' }]</code>. وفي <code dir=\"ltr\">pluck</code>، تربط <code dir=\"ltr\">K extends keyof T</code> المفتاح بالعناصر، وتبحث <code dir=\"ltr\">T[K]</code> عن نوع تلك الخاصية، فتعطي <code dir=\"ltr\">'customer'</code> النوع <code dir=\"ltr\">string[]</code> وتُرفض <code dir=\"ltr\">'email'</code>.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ، بما فيها كل اختبارات الأنواع","sorts-copy":"<code dir=\"ltr\">sortByTotal</code> ترتّب تصاعديًا دون تعديل مدخلها","indexes":"<code dir=\"ltr\">indexById</code> تربط كل معرّف بعنصره","plucks":"<code dir=\"ltr\">pluck</code> و<code dir=\"ltr\">lastItem</code> ما زالتا تعملان وقت التشغيل"}},"words":627,"readMinutes":4}