«Type 'string' is not assignable to type» — ما هو ولماذا أنت هنا؟
\n\nإذا سبق لك أن كتبت كودًا بلغة TypeScript، فبالتأكيد واجهت ذلك الخط الأحمر في محرر الأكواد مع الرسالة:
\n\nType 'string' is not assignable to type 'SomeType'.\n\nهذا الخطأ هو بمثابة «تحية» من النظام النوعي الصارم. قد يخيف المبتدئين، لكنه في الحقيقة أفضل صديق للمطور. في هذا المقال، سنشرح لماذا يظهر خطأ Type 'string' is not assignable to type، وكيفية إصلاحه، وما هي أفضل الطرق للتعامل مع string literal types وunion types وgenerics. المقال مخصص للموقع التعليمي pythonlib.ru، لكن الأمثلة مكتوبة بلغة TypeScript.
\n\n1. جذر المشكلة: لماذا يعترض TypeScript على السلاسل النصية؟
\n\nTypeScript هي طبقة فوق JavaScript تضيف الكتابة النوعية الثابتة (static typing). عندما تكتب:
\n\nlet role: 'admin' | 'user' = 'admin';\nrole = 'moderator'; // خطأ: Type '"moderator"' is not assignable to type '"admin" | "user"'\n\n\nيفهم TypeScript أن المتغير role يمكنه قبول قيمتين فقط: 'admin' أو 'user'. عندما تحاول تعيين 'moderator'، يظهر الخطأ. لكن لماذا يكتب «Type 'string' is not assignable to type»؟
السبب هو أن القيمة النصية 'moderator' لها نوع string، بينما النوع المتوقع هو 'admin' | 'user'. TypeScript يقارن الأنواع، وليس القيم. يرى: «أنت تعطيني سلسلة نصية عادية، وأنا أنتظر سلاسل محددة». من هنا تأتي الرسالة.
1.1. مثال بسيط للفهم
\n\nلننظر إلى حالة أساسية:
\n\ntype Status = 'active' | 'inactive';\n\nfunction updateStatus(status: Status) {\n console.log(status);\n}\n\nconst myStatus = 'active';\nupdateStatus(myStatus); // صحيح\n\nconst anotherStatus = 'pending';\nupdateStatus(anotherStatus); // خطأ: Type 'string' is not assignable to type 'Status'\n\n\nفي الحالة الأخيرة، يُستنتج أن المتغير anotherStatus من النوع string، وليس 'pending'. TypeScript لا يعرف أن 'pending' هو قيمة حرفية (literal). يرى فقط النوع string. يمكن إصلاح ذلك بتحديد النوع صراحة:
const anotherStatus: 'pending' = 'pending'; // الآن يعمل\n\n\n2. مقارنة الحلول: كيفية تجنب أو إصلاح الخطأ
\n\nهناك عدة طرق للتعامل مع خطأ Type 'string' is not assignable to type. سنقارنها من حيث البساطة والأمان والأداء.
\n\n2.1. الحل الأول: as const — تجميد القيمة الحرفية
\n\nالطريقة الأكثر أناقة هي استخدام as const. إنها «تجمّد» القيمة كنوع حرفي:
const myStatus = 'active' as const;\nupdateStatus(myStatus); // صحيح\n\n\nالمزايا: بسيط، قابل للقراءة، آمن.
\nالعيوب: يعمل فقط مع const، وليس مع let.
2.2. الحل الثاني: Type Assertion (as) — التحويل الصريح
\n\nيمكنك إخبار TypeScript: «أنا أعرف ما أفعله»:
\n\nconst someString: string = 'active';\nupdateStatus(someString as Status); // صحيح\n\n\nالمزايا: حل سريع.
\nالعيوب: أنت تعطّل التحقق من الأنواع. إذا مررت قيمة خاطئة، سيظهر الخطأ فقط في وقت التشغيل (runtime).
2.3. الحل الثالث: Type Guard (حارس النوع)
\n\nنكتب دالة تتحقق مما إذا كانت السلسلة النصية حالة صالحة:
\n\nfunction isStatus(value: string): value is Status {\n return value === 'active' || value === 'inactive';\n}\n\nfunction safeUpdate(value: string) {\n if (isStatus(value)) {\n updateStatus(value); // TypeScript يعرف أن value من النوع Status\n } else {\n throw new Error('حالة غير صالحة');\n }\n}\n