{"id":"l-at-5-1","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"اختر خيارات الـ compiler التي تجعل أنواعك جديرة بالثقة، من strict وnoUncheckedIndexedAccess إلى verbatimModuleSyntax وmoduleResolution bundler، وفعّلها في قاعدة كود قائمة دون إيقاف العمل.","html":"<p>كل نوع في هذه الدورة جدير بالثقة بقدر إعدادات الـ compiler التي خلفه فقط. نوع <code dir=\"ltr\">Order</code> نفسه الذي يلتقط رقم تتبّع ناقصًا تحت tsconfig ما، يترك انهيارًا يمرّ تحت آخر. حين انضممت إلى عملية الترحيل التي قُدتها لاحقًا، كان في tsconfig القيمة <code dir=\"ltr\">strict: false</code>، وكان الفريق فخورًا بتغطية TypeScript بنسبة 100%. معظم تلك التغطية كانت تفحص القليل جدًا.</p>\n<p>هذا الدرس عن اختيار الخيارات عن قصد، وعن تفعيلها في قاعدة كود حقيقية دون تجميد للعمل ستة أسابيع.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">strict: خط الأساس</h2>\n<p><code dir=\"ltr\">strict</code> ليس فحصًا واحدًا بل عائلة. أهمّها في العمل اليومي:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>الخيار</th>\n<th>ما يلتقطه</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">strictNullChecks</code></td>\n<td>استخدام قيمة قد تكون <code dir=\"ltr\">null</code> أو <code dir=\"ltr\">undefined</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">noImplicitAny</code></td>\n<td>معاملات ومتغيّرات نوعها <code dir=\"ltr\">any</code> بصمت</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">strictFunctionTypes</code></td>\n<td>أنواع معاملات callback غير آمنة (<a href=\"/ar/lesson/advanced-typescript/l-at-1-2\">الدرس 1-2</a>)</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">useUnknownInCatchVariables</code></td>\n<td>معاملة القيمة الملتقَطة على أنها <code dir=\"ltr\">Error</code> دون فحص</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">strictPropertyInitialization</code></td>\n<td>حقول أصناف لا يُسند إليها شيء أبدًا</td>\n</tr>\n</tbody>\n</table></div><p>منذ TypeScript 6.0، صار <code dir=\"ltr\">strict</code> مفعَّلًا افتراضيًا، فيحصل عليه المشروع الجديد دون أن يطلبه. وفي المشاريع الأقدم يبقى أثمن سطر في الملف. وكل ما تبقّى في هذا الدرس يفترض وجوده.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">خياران يتركهما strict</h2>\n<p><strong><code dir=\"ltr\">noUncheckedIndexedAccess</code></strong> يجعل الوصول بالفهرس صادقًا. معه يكون <code dir=\"ltr\">lines[0]</code> من نوع <code dir=\"ltr\">Line | undefined</code>، و<code dir=\"ltr\">STOCK[sku]</code> من نوع <code dir=\"ltr\">number | undefined</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> first</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cart.lines[</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\">first.sku;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: 'first' is possibly 'undefined'.</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\"> available</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\"> STOCK</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[sku];</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Type 'number | undefined' is not assignable to type 'number'.</span></span></code></pre></div>\n<p>“Cannot read properties of undefined” هو أكثر أخطاء وقت التشغيل شيوعًا في JavaScript، والمصفوفات الفارغة والمفاتيح الناقصة مصادره المفضّلة. وهذا الخيار يلتقطها. والثمن بعض الفحوص الإضافية حيث تعرف أن الفهرس صالح (داخل <code dir=\"ltr\">for (let i = 0; i &lt; xs.length; i++)</code>)؛ أما حلقات <code dir=\"ltr\">for…of</code> ودوال المصفوفات مثل <code dir=\"ltr\">map</code> فلا تتأثّر.</p>\n<p><strong><code dir=\"ltr\">exactOptionalPropertyTypes</code></strong> يفصل “الغائبة” عن “الموجودة لكن <code dir=\"ltr\">undefined</code>”. الـ endpoint الخاص بـ PATCH في Cartwheel يعامل <code dir=\"ltr\">{ couponCode: undefined }</code> على أنها “أزِل الكوبون” و<code dir=\"ltr\">{}</code> على أنها “اتركه كما هو”. دون الخيار، للاثنين النوع نفسه. ومعه:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderPatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">couponCode</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> patch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderPatch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { couponCode: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Type '{ couponCode: undefined; }' is not assignable to type 'OrderPatch'</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// with 'exactOptionalPropertyTypes: true'. …</span></span></code></pre></div>\n<p>إن كنت تقصد فعلًا “قد تكون undefined صراحةً”، فقلها: <code dir=\"ltr\">couponCode?: string | undefined</code>. هذا الخيار أكثر إزعاجًا عند تبنّيه، لأن أنواع مكتبات كثيرة كُتبت دونه، ففعّله بعد الآخرين.</p>\n<p>صار <code dir=\"ltr\">tsc --init</code> في TypeScript 5.9 يفعّل الخيارين افتراضيًا في الـ tsconfig الذي يولّده، وهذا تلميح قوي إلى الاتجاه الذي يسير فيه النظام البيئي.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-at-5-1-t1\"><defs><marker id=\"arrow-l-at-5-1-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-5-1-t1\">ثلاث طبقات متراكبة. الطبقة السفلى، strict، هي خط الأساس لفحوص null وany. والطبقة الوسطى تضيف noUncheckedIndexedAccess وexactOptionalPropertyTypes وnoImplicitOverride لصحة أكثر صرامة. والطبقة العليا تغطّي سلوك الوحدات: verbatimModuleSyntax وmoduleResolution bundler وerasableSyntaxOnly.</title>\n  <rect class=\"d-box-primary\" x=\"60\" y=\"20\" width=\"580\" height=\"56\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"44\" text-anchor=\"middle\">كيف تُولَّد الوحدات وتُحَلّ</text>\n  <text class=\"d-code\" x=\"350\" y=\"66\" text-anchor=\"middle\">verbatimModuleSyntax · moduleResolution: bundler · erasableSyntaxOnly</text>\n  <rect class=\"d-box-accent\" x=\"60\" y=\"88\" width=\"580\" height=\"56\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"112\" text-anchor=\"middle\">صحة تتجاوز strict</text>\n  <text class=\"d-code\" x=\"350\" y=\"134\" text-anchor=\"middle\">noUncheckedIndexedAccess · exactOptionalPropertyTypes · noImplicitOverride</text>\n  <rect class=\"d-box-success\" x=\"60\" y=\"156\" width=\"580\" height=\"56\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"350\" y=\"180\" text-anchor=\"middle\">خط الأساس</text>\n  <text class=\"d-code\" x=\"350\" y=\"202\" text-anchor=\"middle\">strict (افتراضي منذ TypeScript 6.0)</text>\n</svg><figcaption>ثلاث طبقات لـ tsconfig حديث</figcaption></figure>\n<h2 id=\"sec-3\" tabindex=\"-1\">خيارات الوحدات: قل ما تعنيه</h2>\n<p><strong><code dir=\"ltr\">verbatimModuleSyntax</code></strong> (TypeScript 5.0) يجعل الـ imports الناتجة مطابقة لما كتبته. أي import دون المعدِّل <code dir=\"ltr\">type</code> يُحفظ؛ والـ imports المعلَّمة بـ <code dir=\"ltr\">type</code> تُمحى. هذا مهم لأن أدوات مثل Vite وesbuild وتجريد الأنواع المدمج في Node تترجم ملفًا واحدًا في كل مرة، ولا تستطيع البحث عمّا إذا كان <code dir=\"ltr\">Order</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\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { Order } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './orders'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: 'Order' is a type and must be imported using a type-only import</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// when 'verbatimModuleSyntax' is enabled.</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">import</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Order, placeOrder } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">from</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> './orders'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// fine</span></span></code></pre></div>\n<p><strong><code dir=\"ltr\">moduleResolution</code></strong> يخبر TypeScript كيف يجد الـ imports الخاصة بك. طابِقه مع الأداة التي تحلّها فعلًا. لتطبيق يبنيه Vite أو bundler آخر، استخدم <code dir=\"ltr\">&quot;bundler&quot;</code> (مع <code dir=\"ltr\">&quot;module&quot;: &quot;esnext&quot;</code> أو <code dir=\"ltr\">&quot;preserve&quot;</code>): يفهم <code dir=\"ltr\">exports</code> في الحزم ولا يتطلّب امتدادات الملفات. وللكود الذي يشغّله Node مباشرة، مثل خادم أو أداة سطر أوامر، استخدم <code dir=\"ltr\">&quot;module&quot;: &quot;nodenext&quot;</code>، الذي يتبع قواعد Node الحقيقية، بما فيها امتدادات <code dir=\"ltr\">.js</code> في الـ imports النسبية. الإعداد القديم <code dir=\"ltr\">&quot;node&quot;</code> (المسمّى أيضًا <code dir=\"ltr\">node10</code>) أقدم من <code dir=\"ltr\">exports</code> في الحزم؛ وهو مهمل في TypeScript 6.0 ومُزال في 7.0، مع <code dir=\"ltr\">baseUrl</code> و<code dir=\"ltr\">target: &quot;es5&quot;</code>.</p>\n<p><strong><code dir=\"ltr\">erasableSyntaxOnly</code></strong> (TypeScript 5.8) يرفض الميزات القليلة في TypeScript التي تولّد كودًا وقت التشغيل: <code dir=\"ltr\">enum</code>، والـ namespaces التي فيها قيم، وخصائص معاملات المُنشئ (parameter properties)، وصيغتَي CommonJS <code dir=\"ltr\">import x = require(…)</code> و<code dir=\"ltr\">export =</code>. فعّله إن كان كودك يمرّ عبر تجريد الأنواع في Node، أو إن كنت تريد أن يبقى TypeScript “JavaScript مع أنواع”.</p>\n<p>بجمعها معًا، يبدو tsconfig لتطبيق مُجمَّع بـ bundler في 2026 هكذا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"json\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">tsconfig.json</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:#1F2328;--shiki-dark:#E6EDF3\">{</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"compilerOptions\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: {</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"target\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"es2022\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"module\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"esnext\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"moduleResolution\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"bundler\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">    \"verbatimModuleSyntax\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">    \"erasableSyntaxOnly\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">    \"noEmit\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">    \"strict\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">    \"noUncheckedIndexedAccess\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">    \"exactOptionalPropertyTypes\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">    \"noImplicitOverride\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">    \"skipLibCheck\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </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:#116329;--shiki-dark:#7EE787\">    \"jsx\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"react-jsx\"</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  },</span></span>\n<span class=\"line\"><span style=\"color:#116329;--shiki-dark:#7EE787\">  \"include\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"src\"</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\">noEmit</code> لأن الـ bundler هو من ينتج JavaScript و<code dir=\"ltr\">tsc</code> يفحص فقط. و<code dir=\"ltr\">skipLibCheck</code> لأن فحص كل ملف <code dir=\"ltr\">.d.ts</code> في <code dir=\"ltr\">node_modules</code> يكلّف وقتًا ويجد مشكلات لا تستطيع إصلاحها.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">تفعيل الخيارات في قاعدة كود قائمة</h2>\n<p>فعّل خيارًا في قاعدة كود كبيرة وقد تحصل على 2,000 خطأ. لا تُصلحها كلها في pull request واحد. قِس أولًا (<code dir=\"ltr\">tsc --noEmit | grep -c &quot;error TS&quot;</code>)، ثم اختر أحد نهجين. إمّا أن تُصلح مجلدًا بعد مجلد باستخدام tsconfig منفصل يمتدّ من الرئيسي ولا يضمّ إلا المجلدات النظيفة، أو أن تفعّل الخيار في كل مكان وتعلّم الأخطاء الموجودة بـ <code dir=\"ltr\">// @ts-expect-error</code> مع مرجع لتذكرة، فيُحاسَب الكود الجديد بالمعيار الجديد فورًا ولا يتحرّك العدد القديم إلا نزولًا.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>إطفاء الصرامة لفكّ انسداد إصدار</span></p>\n<p>ضبط <code dir=\"ltr\">strict: false</code> “مؤقتًا” لإخراج بناء هو الطريقة التي ينتهي بها الحال بقواعد الكود إلى سنوات من الكود غير المفحوص: لا يُفحص أي جديد ما دام مُطفأً، وعدد الأخطاء حين تعيد تفعيله لا يزيد إلا نموًا. بدلًا من ذلك أخفِ الأخطاء منفردة بـ <code dir=\"ltr\">@ts-expect-error</code> مع سبب؛ فيبقى كلٌّ منها مرئيًا وقابلًا للبحث والإزالة.</p>\n</aside>\n<p>التمرين سيناريو: ثلاثة أخطاء حقيقية، وتختار أنت الخيارات التي كانت ستلتقطها. بيئة التجربة نفسها تعمل بـ <code dir=\"ltr\">strict</code> ومكتبة ES2022، ولهذا تحتاج بعض الميزات اللاحقة إلى تصريح صغير لتعمل فيها، كما سترى في الدرس القادم.</p>\n","toc":[{"id":"sec-1","text":"strict: خط الأساس","level":2},{"id":"sec-2","text":"خياران يتركهما strict","level":2},{"id":"sec-3","text":"خيارات الوحدات: قل ما تعنيه","level":2},{"id":"sec-4","text":"تفعيل الخيارات في قاعدة كود قائمة","level":2}],"takeaways":["<code dir=\"ltr\">strict</code> عائلة من الفحوص (فحوص null، والـ any الضمني، وفحوص معاملات الدوال وغيرها)، وهو الافتراضي منذ TypeScript 6.0.","<code dir=\"ltr\">noUncheckedIndexedAccess</code> يضيف <code dir=\"ltr\">undefined</code> إلى عمليات البحث في المصفوفات والـ records، فيلتقط أكثر انهيار شيوعًا يفوته <code dir=\"ltr\">strict</code>.","<code dir=\"ltr\">exactOptionalPropertyTypes</code> يفصل الخاصية الغائبة عن الخاصية المضبوطة صراحةً على <code dir=\"ltr\">undefined</code>، وهذا مهم لأجسام طلبات PATCH والـ spreads.","<code dir=\"ltr\">verbatimModuleSyntax</code> يجعل الـ imports الناتجة مطابقة لما كتبته، فيجب أن تقول الـ imports الخاصة بالأنواع فقط <code dir=\"ltr\">import type</code>.","استخدم <code dir=\"ltr\">moduleResolution: &quot;bundler&quot;</code> للتطبيقات التي يبنيها Vite أو bundler آخر، و<code dir=\"ltr\">nodenext</code> للكود الذي يشغّله Node مباشرة."],"further":[{"title":"TSConfig Reference","url":"https://www.typescriptlang.org/tsconfig/"},{"title":"noUncheckedIndexedAccess","url":"https://www.typescriptlang.org/tsconfig/noUncheckedIndexedAccess.html"},{"title":"Modules - Choosing Compiler Options","url":"https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html"},{"title":"TypeScript 6.0 release notes","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html"}],"quiz":[{"q":"<p>مع تفعيل <code dir=\"ltr\">strict</code> وإيقاف <code dir=\"ltr\">noUncheckedIndexedAccess</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> first</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cart.lines[</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\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(first.sku);</span></span></code></pre></div>\n","options":[{"text":"يمرّ في الترجمة، ويرمي “Cannot read properties of undefined” وقت التشغيل.","why":"صحيح. دون هذا الخيار، نوع <code dir=\"ltr\">lines[0]</code> هو <code dir=\"ltr\">Line</code> لا <code dir=\"ltr\">Line | undefined</code>، فتكون الحالة الفارغة غير مرئية."},{"text":"يفشل في الترجمة، لأن <code dir=\"ltr\">strictNullChecks</code> يعرف أن المصفوفات قد تكون فارغة.","why":"<code dir=\"ltr\">strictNullChecks</code> لا يتتبّع <code dir=\"ltr\">null</code> و<code dir=\"ltr\">undefined</code> إلا في الأنواع المعلَنة. الوصول بالفهرس متفائل ما لم يُفعَّل <code dir=\"ltr\">noUncheckedIndexedAccess</code>."},{"text":"يمرّ في الترجمة ويطبع <code dir=\"ltr\">undefined</code>.","why":"قراءة <code dir=\"ltr\">.sku</code> من <code dir=\"ltr\">undefined</code> ترمي خطأً؛ لا تُعيد <code dir=\"ltr\">undefined</code>."},{"text":"يفشل في الترجمة، لأن <code dir=\"ltr\">strict</code> يمنع الوصول بالفهرس.","why":"الوصول بالفهرس مسموح دائمًا؛ الخيار يغيّر فقط النوع الناتج."}],"answer":0},{"q":"<p><code dir=\"ltr\">type Patch = { couponCode?: string }</code>. مع تفعيل <code dir=\"ltr\">exactOptionalPropertyTypes</code>، أيّ قيمة تُرفض؟</p>","options":[{"text":"<code dir=\"ltr\">{}</code>","why":"حذف خاصية اختيارية مسموح دائمًا."},{"text":"<code dir=\"ltr\">{ couponCode: 'LOYAL5' }</code>","why":"النص هو بالضبط ما تسمح به الخاصية."},{"text":"<code dir=\"ltr\">{ couponCode: undefined }</code>","why":"صحيح. يعامل الخيار “موجودة بقيمة undefined” على أنها مختلفة عن “غائبة”؛ أعلن <code dir=\"ltr\">couponCode?: string | undefined</code> إن كنت تقصد السماح بها."}],"answer":2},{"q":"<p>في ملفك <code dir=\"ltr\">import { Order, placeOrder } from './orders'</code> حيث <code dir=\"ltr\">Order</code> نوع. مع تفعيل <code dir=\"ltr\">verbatimModuleSyntax</code>، ماذا تكتب؟</p>","options":[{"text":"لا شيء يتغيّر؛ يُزيل الـ compiler <code dir=\"ltr\">Order</code> من الناتج تلقائيًا.","why":"هذا الحذف التلقائي هو بالضبط ما يُطفئه الخيار، كي تطابق الـ imports الناتجة المصدر."},{"text":"<code dir=\"ltr\">import { type Order, placeOrder } from './orders'</code>","why":"صحيح. المعدِّل <code dir=\"ltr\">type</code> يعلّم <code dir=\"ltr\">Order</code> على أنه للأنواع فقط، فيُمحى بينما تبقى <code dir=\"ltr\">placeOrder</code>."},{"text":"<code dir=\"ltr\">import * as orders from './orders'</code> واستخدم <code dir=\"ltr\">orders.Order</code>","why":"الـ import على هيئة namespace يعمل مع القيم، لكنه لا يحلّ تعليم “للأنواع فقط” الذي يطلبه الخيار."},{"text":"فعّل <code dir=\"ltr\">esModuleInterop</code>.","why":"<code dir=\"ltr\">esModuleInterop</code> يتعلّق بالـ imports الافتراضية من CommonJS ولا علاقة له بالـ imports الخاصة بالأنواع فقط."}],"answer":1},{"q":"<p>في tsconfig تطبيقك المبني بـ Vite القيمة <code dir=\"ltr\">&quot;moduleResolution&quot;: &quot;node&quot;</code>. إلى ماذا يجب أن تغيّرها، ولماذا؟</p>","options":[{"text":"<code dir=\"ltr\">&quot;classic&quot;</code>، لأنها الأكثر تساهلًا.","why":"<code dir=\"ltr\">classic</code> أقدم من أعراف npm، ومثل <code dir=\"ltr\">node</code> هي مهملة في TypeScript 6.0 ومُزالة في 7.0."},{"text":"<code dir=\"ltr\">&quot;nodenext&quot;</code>، لأن Vite يعمل على Node.","why":"Vite يعمل على Node، لكن كود تطبيقك يحلّه الـ bundler، و<code dir=\"ltr\">nodenext</code> سيطلب امتدادات ملفات لا يحتاجها الـ bundler."},{"text":"<code dir=\"ltr\">&quot;bundler&quot;</code>، لأنها تحاكي طريقة Vite في حلّ الـ imports، بما فيها <code dir=\"ltr\">exports</code> في الحزم، و<code dir=\"ltr\">node</code> مهملة.","why":"صحيح. <code dir=\"ltr\">bundler</code> تطابق الأداة التي تحلّ الـ imports فعلًا، و<code dir=\"ltr\">node</code> (<code dir=\"ltr\">node10</code>) مُزالة في TypeScript 7.0."}],"answer":2}],"exercise":{"kind":"choice","multiple":true,"options":["unchecked-index","exact-optional","implicit-override","skip-lib-check","verbatim-modules","allow-js"],"correct":["unchecked-index","exact-optional","implicit-override"],"title":"أيّ الخيارات كانت ستلتقط هذه الأخطاء؟","prompt":"<p>نظام الدفع في Cartwheel يُترجم أصلًا مع <code dir=\"ltr\">strict: true</code>. ومع ذلك وصلت هذه الأخطاء الثلاثة إلى الإنتاج في الربع الماضي:</p>\n<ol>\n<li><strong>انهيار السلة الفارغة.</strong> كان ملخّص الطلب يقرأ <code dir=\"ltr\">cart.lines[0].sku</code> لعرض صورة مصغّرة. ومع سلة فارغة، انهارت الصفحة برسالة <em>Cannot read properties of undefined</em>.</li>\n<li><strong>اختفاء الكوبونات.</strong> كان نموذج يرسل <code dir=\"ltr\">{ couponCode: formValue.coupon }</code> إلى الـ endpoint الخاص بـ PATCH. ونوع <code dir=\"ltr\">formValue.coupon</code> هو <code dir=\"ltr\">string | undefined</code> (فالحقل اختياري)؛ وحين كان الحقل فارغًا، كانت قيمته <code dir=\"ltr\">undefined</code>، والـ API يعامل <code dir=\"ltr\">couponCode</code> الموجودة بقيمة undefined على أنها “أزِل الكوبون”، فخسر العملاء كوبونات كانوا قد طبّقوها سابقًا.</li>\n<li><strong>خطأ ضريبي صامت.</strong> أُعيدت تسمية <code dir=\"ltr\">PriceCalculator.applyTax()</code> إلى <code dir=\"ltr\">addTax()</code> في الصنف الأساسي. وما زال الصنف الفرعي <code dir=\"ltr\">GulfPriceCalculator</code> يعرّف <code dir=\"ltr\">applyTax()</code>، التي لم تعد تتجاوز شيئًا، فطُبّقت على طلبات الخليج النسبة الافتراضية للضريبة.</li>\n</ol>\n<p>اختر <strong>كل</strong> خيار في الـ compiler كان سيحوّل، إذا أُضيف إلى <code dir=\"ltr\">strict</code>، واحدًا على الأقل من هذه الأخطاء إلى خطأ ترجمة.</p>\n","hints":["فكّر في نوع <code dir=\"ltr\">cart.lines[0]</code> اليوم، وأيّ خيار يغيّره.","خيار واحد يميّز بين <code dir=\"ltr\">{}</code> و<code dir=\"ltr\">{ couponCode: undefined }</code> لخاصية اختيارية.","خيار واحد يتطلّب الكلمة المفتاحية <code dir=\"ltr\">override</code> على كل method تتجاوز method في الصنف الأساسي، فيتوقّف التجاوز المتقادم عن المرور في الترجمة."],"explanation":"<p><code dir=\"ltr\">noUncheckedIndexedAccess</code> يجعل نوع <code dir=\"ltr\">cart.lines[0]</code> هو <code dir=\"ltr\">Line | undefined</code>، فيفرض فحصًا قبل <code dir=\"ltr\">.sku</code> (الخطأ 1). و<code dir=\"ltr\">exactOptionalPropertyTypes</code> يرفض إسناد <code dir=\"ltr\">undefined</code> إلى <code dir=\"ltr\">couponCode?: string</code>، فكان على النموذج أن يحذف المفتاح أو يعلن <code dir=\"ltr\">string | undefined</code> صراحة (الخطأ 2). و<code dir=\"ltr\">noImplicitOverride</code> يتطلّب <code dir=\"ltr\">override applyTax()</code> في الصنف الفرعي؛ وما إن تُعاد تسمية الـ method الأساسية حتى يصبح <code dir=\"ltr\">override</code> على method لا تتجاوز شيئًا خطأً (الخطأ 3). أما <code dir=\"ltr\">skipLibCheck</code> فيقلّل الفحص، و<code dir=\"ltr\">verbatimModuleSyntax</code> يتعلّق بطريقة توليد الـ imports، و<code dir=\"ltr\">allowJs</code> يُدخل ملفات JavaScript إلى البرنامج؛ ولا يلتقط أيٌّ منها هذه الأخطاء.</p>\n","checkLabels":{},"optionText":{"unchecked-index":{"text":"<code dir=\"ltr\">noUncheckedIndexedAccess</code>","why":"صحيح. يجعل <code dir=\"ltr\">cart.lines[0]</code> قد تكون <code dir=\"ltr\">undefined</code>، فتصبح قراءة <code dir=\"ltr\">.sku</code> دون فحص خطأً (الخطأ 1)."},"exact-optional":{"text":"<code dir=\"ltr\">exactOptionalPropertyTypes</code>","why":"صحيح. يمنع كتابة <code dir=\"ltr\">undefined</code> في <code dir=\"ltr\">couponCode?: string</code>، وهي بالضبط القيمة التي أزالت الكوبونات (الخطأ 2)."},"implicit-override":{"text":"<code dir=\"ltr\">noImplicitOverride</code>","why":"صحيح. معه يجب أن يعلّم الصنف الفرعي <code dir=\"ltr\">override applyTax()</code>، وتفشل تلك العلامة ما إن تُعاد تسمية الـ method الأساسية (الخطأ 3)."},"skip-lib-check":{"text":"<code dir=\"ltr\">skipLibCheck</code>","why":"يتخطّى فحص أنواع ملفات التصريحات، ومعظمها في <code dir=\"ltr\">node_modules</code>. يجعل الفحص أسرع وأكثر تساهلًا، ولا يجعله أكثر صرامة أبدًا."},"verbatim-modules":{"text":"<code dir=\"ltr\">verbatimModuleSyntax</code>","why":"يتحكّم في الـ imports التي تُحفظ في JavaScript الناتجة. إنه قيّم للـ bundlers، لكنه لا يفحص الوصول إلى الخصائص ولا التجاوزات."},"allow-js":{"text":"<code dir=\"ltr\">allowJs</code>","why":"يتيح لملفات <code dir=\"ltr\">.js</code> الانضمام إلى البرنامج. إنه أداة ترحيل ولا يضيف أي فحوص إلى كود TypeScript الموجود."}}},"words":731,"readMinutes":4}