التحكم في نوع العناصر في الكائنات المفهرسة TypeScript

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

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

ابدأ الدورة

لماذا هذا الموضوع يحظى باهتمام كبير على Stack Overflow؟

\n\n

إذا كنت قد كتبت كودًا بلغة TypeScript وحاولت إنشاء كائن حيث جميع القيم يجب أن تكون من نوع واحد، فمن المحتمل أنك واجهت هذه المشكلة. سؤال "Enforcing the type of the indexed members of a Typescript object" حصل على آلاف المشاهدات على Stack Overflow ليس من قبيل الصدفة - إنها مشكلة كلاسيكية للمطورين الذين ينتقلون من JavaScript.

\n\n

تخيل: لديك قاموس تخزين مؤقت، أو إعدادات بمفاتيح ديناميكية، أو مجرد خريطة بأرقام. تريد أن يتأكد TypeScript من أن كل قيمة في الكائن هي نص، أو رقم، أو نوع معقد. لكن المترجم يسمح بمرور الأخطاء. هل هذا مألوف؟

\n\n

في هذه المقالة، سنستعرض جميع الطرق الرئيسية لحل هذه المشكلة: من Index Signatures البسيطة إلى Mapped Types المتقدمة. ستتعرف متى تستخدم كل طريقة وما هي المخاطر التي تنتظرك.

\n\n

المشكلة: كائن بمفاتيح ديناميكية

\n\n

لنفترض أننا نكتب نظامًا لإدارة المستخدمين. لدينا كائن حيث المفاتيح هي معرفات المستخدمين، والقيم هي أسمائهم:

\n\n
interface UserCache {\n  [userId: string]: string;\n}\n\nconst cache: UserCache = {\n  'user_1': 'أحمد',\n  'user_2': 'سارة',\n  // 'user_3': 123; // خطأ! يجب أن يكون نصًا\n};\n\ncache['user_4'] = 'خالد'; // صحيح\ncache['user_5'] = 42; // خطأ! النوع 'number' غير قابل للتخصيص لـ 'string'\n
\n\n

يبدو أن كل شيء يعمل. لكن هناك تفصيل: Index Signature يسمح بأي مفاتيح نصية. ماذا لو أردنا تقييد مجموعة المفاتيح المسموح بها؟

\n\n

الطريقة 1: Index Signature - الطريقة الكلاسيكية

\n\n

Index Signature هي أبسط طريقة لتحديد نوع جميع قيم الكائن. الصيغة:

\n\n
interface StringMap {\n  [key: string]: string;\n}\n\nconst map: StringMap = {\n  hello: 'العالم',\n  foo: 'bar',\n};\n
\n\n

المزايا:

\n
    \n
  • البساطة وسهولة القراءة
  • \n
  • تعمل مع أي عدد من المفاتيح
  • \n
\n\n

العيوب:

\n
    \n
  • لا يمكن تقييد مجموعة المفاتيح
  • \n
  • جميع القيم يجب أن تكون من نوع واحد (أو union)
  • \n
  • لا تعمل مع قيم undefined (يجب الحذر)
  • \n
\n\n

خطأ شائع للمبتدئين: محاولة إضافة دالة إلى هذا الكائن:

\n\n
interface BadMap {\n  [key: string]: string;\n  length: number; // خطأ! 'number' غير قابل للتخصيص لـ 'string'\n}\n
\n\n

هذا لن يعمل لأن length يخالف النوع العام. إذا كنت بحاجة لخلط الأنواع، استخدم union أو Mapped Types.

\n\n

الطريقة 2: Record - المساعد المدمج

\n\n

يوفر TypeScript نوعًا مدمجًا Record<K, V> يفعل نفس الشيء ولكن بصيغة أكثر إيجازًا:

\n\n
type StringRecord = Record<string, string>;\n\nconst config: StringRecord = {\n  apiUrl: 'https://api.example.com',\n  timeout: '5000', // صحيح، نص\n  // port: 8080, // خطأ!\n};\n\n// يمكن تقييد المفاتيح:\ntype AllowedKeys = 'name' | 'email' | 'phone';\ntype UserRecord = Record<AllowedKeys, string>;\n\nconst user: UserRecord = {\n  name: 'أحمد',\n  email: 'ahmed@example.com',\n  phone: '+1234567890',\n  // age: '30', // خطأ! 'age' ليس ضمن AllowedKeys\n};\n
\n\n

متى تستخدم:

\n
    \n
  • عند الحاجة لإنشاء قاموس مقيد الأنواع بسرعة
  • \n
  • عندما تكون المفاتيح مجموعة محددة من القيم (union)
  • \n
  • للإعدادات ذات الحقول المعروفة
  • \n
\n\n

العيب: Record لا يسمح بتحديد أنواع مختلفة لمفاتيح مختلفة. لهذا يوجد Mapped Types.

\n\n

الطريقة 3: Map - البديل من ES6

\n\n

إذا كنت تستخدم Map من JavaScript، فإن TypeScript يقوم بتحديد أنواعه بشكل ممتاز

المدونات

توصيات الكتب