خطأ TypeScript: فهرس من نوع union - حل للمبتدئين

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا يعترض TypeScript على الكود الخاص بك؟

\n\n

هل سبق لك أن كتبت كودًا بلغة TypeScript يبدو منطقيًا تمامًا، ولكن المترجم فجأة يظهر خطأً مخيفًا؟ إذا واجهت الرسالة «An index signature parameter type cannot be a union type. Consider using a mapped object type instead» — فأنت لست وحدك. هذا الخطأ هو أحد أكثر الأسئلة شيوعًا على Stack Overflow (آلاف المشاهدات!)، وغالبًا ما يحير حتى المطورين ذوي الخبرة.

\n\n

في هذه المقالة، لن نعرض فقط كيفية إصلاح الخطأ. سنشرح لماذا TypeScript صارم جدًا، وكيفية استخدام mapped object types لجعل الكود الخاص بك ليس فقط قابلاً للعمل، بل وأكثر أمانًا. هيا بنا!

\n\n

ما هو index signature وما الغرض منه؟

\n\n

تخيل أنك تنشئ كائنًا، حيث المفاتيح هي نصوص والقيم هي أرقام. على سبيل المثال، قاموس بأسعار المنتجات:

\n\n
// كائن عادي\nconst prices = {\n  apple: 100,\n  banana: 50,\n  cherry: 200\n};
\n\n

ولكن ماذا لو أردت أن تقول لـ TypeScript: «اسمع، هذا الكائن يمكن أن يحتوي على أي مفاتيح نصية، وكل القيم ستكون أرقامًا»؟ لهذا نحتاج إلى index signature — بناء جملة خاص يصف نمطًا لجميع خصائص الكائن:

\n\n
// Index signature: [key: string]: number\ninterface PriceMap {\n  [key: string]: number;\n}\n\nconst prices: PriceMap = {\n  apple: 100,\n  banana: 50\n};
\n\n

هنا [key: string]: number هو index signature. وهو يقول: «المفاتيح نصوص، والقيم أرقام». الأمر بسيط، أليس كذلك؟

\n\n

ما المشكلة مع union-أنواع؟

\n\n

الآن تخيل أنك تريد تقييد المفاتيح المحتملة إلى خيارين فقط: 'name' أو 'age'. يبدو من المنطقي كتابة ما يلي:

\n\n
// ❌ خطأ! لا يمكن فعل ذلك\ninterface Person {\n  [key: 'name' | 'age']: string | number;\n}
\n\n

وهنا يظهر TypeScript الخطأ نفسه: «An index signature parameter type cannot be a union type. Consider using a mapped object type instead».

\n\n

لماذا؟ لأن index signature مصمم لوصف مجموعة غير محدودة من المفاتيح. إذا كنت تريد تحديد قائمة محددة من المفاتيح، فهذا لم يعد نمطًا، بل وصفًا دقيقًا للهيكل. TypeScript لا يسمح بخلط هذه المفاهيم.

\n\n

كيفية إصلاح الخطأ؟ استخدام Mapped Object Type

\n\n

الحل الذي يقترحه المترجم نفسه هو استخدام mapped object type (نوع الكائن المُعيّن). يبدو الأمر معقدًا، لكنه في الواقع مجرد طريقة «للتكرار» على جميع المفاتيح من union type وإنشاء خاصية لكل منها.

\n\n

إليك كيف يبدو ذلك:

\n\n
// ✅ صحيح: استخدام mapped type\ntype Person = {\n  [K in 'name' | 'age']: K extends 'name' ? string : number;\n};\n\n// أو خيار أبسط إذا كانت أنواع القيم متطابقة:\ntype PersonSimple = {\n  [K in 'name' | 'age']: string | number;\n};
\n\n

دعنا نحلل بناء الجملة [K in 'name' | 'age']:

\n
    \n
  • K — متغير يأخذ بدوره كل قيمة من union type ('name'، ثم 'age').
  • \n
  • in — كلمة مفتاحية تبدأ «حلقة» عبر الأنواع.
  • \n
  • 'name' | 'age' — مجموعة المفاتيح لدينا.
  • \n
\n\n

ونتيجة لذلك، سينشئ TypeScript كائنًا بخاصيتين محددتين: name و age. اختفى الخطأ!

\n\n

مثال مع أنواع قيم مختلفة

\n\n

غالبًا ما يحدث أن المفاتيح المختلفة يجب أن يكون لها أنواع قيم مختلفة. باستخدام الأنواع الشرطية (conditional types) يتم حل ذلك بأناقة:

\n\n
type Config = {\n  [K in 'host' | 'port' | 'debug']: \n    K extends 'host' ? string :\n    K extends 'port' ? number :\n    boolean;\n};\n\n// الآن:\n                

المدونات

توصيات الكتب