مقدمة: لماذا يصرخ TypeScript على المصفوفة الفارغة؟
\n\nهل سبق لك أن كتبت كودًا في TypeScript، وقمت بتشغيل التجميع، وحصلت على رسالة غامضة: "Argument of type 'X' is not assignable to parameter of type 'never'"؟ إذا كان الأمر كذلك، فأنت لست وحدك. هذا الخطأ هو حجر عثرة حقيقي للمطورين من جميع المستويات. في Stack Overflow، جمع هذا السؤال آلاف المشاهدات، واليوم سنقوم بتحليله مرة واحدة وإلى الأبد.
تخيل الموقف: تعلن عن مصفوفة فارغة، وتحاول إضافة شيء إليها، لكن TypeScript يمنع العملية. هل هذا مألوف؟ دعنا نفهم لماذا يحدث هذا، والأهم من ذلك، كيف نصلحه.
\n\nفي هذه المقالة، لن نقوم فقط بترجمة الوثائق الجافة. سنسير في الطريق من ظهور الخطأ إلى حله الكامل مع أمثلة حقيقية يمكنك تطبيقها فورًا في مشاريعك.
\n\nما هو نوع never في TypeScript؟
\n\nقبل أن نغوص في تفاصيل الأخطاء، دعنا نفهم النوع never نفسه. في TypeScript، يشير هذا النوع إلى "قيمة لا يمكن أن تكون موجودة". يبدو الأمر فلسفيًا، لكنه في الممارسة العملية بسيط:
- \n
neverهو نوع للدوال التي لا تنتهي أبدًا (حلقة لا نهائية) أو التي تطرح دائمًا استثناءً. \n neverهو "قاع" نظام الأنواع. يمكنك تعيينneverلأي شيء، لكن لا يمكن تعيين أي شيء لـnever(باستثناءneverنفسه). \n
الآن تخيل أن TypeScript يرى مصفوفة فارغة ولا يعرف ما هي العناصر التي ستكون فيها. في وضع الكتابة الصارمة (strict: true في ملف tsconfig.json)، يقوم المترجم باستنتاج منطقي: "إذا كانت المصفوفة فارغة ولم يتم تحديد النوع، فهذا يعني أنه لا يمكن أن يكون فيها أي شيء — نوع العناصر هو never".
// مثال: TypeScript يستنتج النوع على أنه never[]\nconst emptyArray = [];\nemptyArray.push("hello"); // خطأ!\n// Argument of type 'string' is not assignable to parameter of type 'never'\n\n\nها هو ذا، ذلك الخطأ نفسه. TypeScript يحذر بأمانة: "لقد قلت إن المصفوفة يمكن أن تحتوي فقط على never، وتحاول إضافة سلسلة نصية. هذا غير مقبول!"
السيناريوهات الرئيسية لظهور الخطأ
\n\n1. مصفوفة فارغة بدون تعليق نوع
\n\nالحالة الأكثر شيوعًا. يقوم المطور بإنشاء مصفوفة فارغة، ويخطط لملئها لاحقًا، لكنه ينسى تحديد النوع.
\n\n// ❌ المشكلة\nconst items = []; // TypeScript يستنتج: any[] (إذا كان strict: false) أو never[] (إذا كان strict: true)\nitems.push({ id: 1, name: "Alice" }); // خطأ عند strict: true\n\n// ✅ الحل: حدد النوع صراحةً\nconst items: Array<{ id: number; name: string }> = [];\nitems.push({ id: 1, name: "Alice" }); // يعمل!\n\n\n2. دوال المصفوفات التي تُرجع never (filter, reduce)
\n\nدالة filter مع شرط غير صحيح أو دالة reduce بدون قيمة ابتدائية يمكن أن تؤدي إلى استنتاج نوع never.
// ❌ مشكلة مع filter\nconst numbers: (number | null)[] = [1, null, 2, null, 3];\nconst onlyNumbers = numbers.filter(item => item !== null); \n// قد يستنتج TypeScript النوع على أنه (number | null)[] أو حتى never[] إذا كان الشرط "معطوبًا"\n\n// ✅ الحل: استخدم حارس النوع (type guard)\nconst onlyNumbersSafe: number[] = numbers.filter((item): item is number => item !== null); \n\n// ❌ مشكلة مع reduce\nconst result = [1, 2, 3].reduce((acc, val) => acc + val); \n// خطأ! TypeScript لا يعرف النوع الابتدائي للمراكم\n\n// ✅ الحل: حدد القيمة الابتدائية\nconst resultSafe = [1, 2, 3].reduce((acc, val) => acc + val, 0); // يعمل!\n\n\n3. الأدوية العامة (Generics) والأنواع الشرطية
\n\nحالة أكثر تعقيدًا — عندما لا يستطيع TypeScript استنتاج النوع بسبب التعميمات المعقدة.
\n\n// ❌ المشكلة\nfunction getFirstElement<T>(arr: T[]): T | undefined {\n return arr[0];\n}\n\nfunction processItems<T>