Record في TypeScript: فهم عملي
\n\nتخيل أنك تكتب تطبيقًا لتعلم الكلمات الإنجليزية. تحتاج إلى تخزين الترجمات: cat → قطة، dog → كلب. كيف تكتب الأنواع (types) بشكل صحيح في TypeScript؟ يمكنك إنشاء واجهة (interface)، لكن ماذا لو كانت المفاتيح غير معروفة مسبقًا؟ أو كان عددها كبيرًا جدًا؟ هنا يأتي دور Record — أحد أكثر أنواع الأدوات (utility types) فائدةً، لكنه غالبًا ما يُقلل من شأنه.
في هذه المقالة، سنشرح ما هو Record type في TypeScript، مع أمثلة واقعية يمكنك تطبيقها فورًا في مشاريعك. لا نظرية مملة — فقط تطبيق عملي.
\n\nما هو Record في TypeScript؟ شرح بسيط
\n\nRecord<K, V> هو نوع أداة (utility type) مدمج ينشئ كائنًا حيث جميع المفاتيح من النوع K وجميع القيم من النوع V.
فكر فيه كـ قاموس مكتوب (typed dictionary) أو خريطة (map) بمجموعة محددة من المفاتيح.
الصيغة الأساسية تبدو كالتالي:
\n\n// كائن عادي\nconst userRoles: Record<string, string> = {\n admin: 'مدير',\n user: 'مستخدم',\n guest: 'ضيف'\n};\n\nهنا Record<string, string> تعني: "كائن حيث المفاتيح نصوص والقيم نصوص أيضًا". بسيط وواضح.
لماذا Record أفضل من الواجهة (interface) العادية؟
\n\n- \n
- مرونة المفاتيح: يمكنك تحديد مجموعة محددة من المفاتيح باستخدام union type. \n
- الإيجاز: لا حاجة لوصف كل حقل يدويًا. \n
- الأمان: سيتحقق TypeScript من أنك تستخدم فقط المفاتيح المسموح بها. \n
Record مقابل الكائن العادي: مثال توضيحي
\n\nلنفترض أن لدينا حالات الطلب: pending، shipped، delivered. بدون Record، ستحتاج إلى كتابة واجهة بثلاثة حقول:
interface OrderStatusMap {\n pending: string;\n shipped: string;\n delivered: string;\n}\n\nconst statusDescriptions: OrderStatusMap = {\n pending: 'قيد الانتظار',\n shipped: 'تم الشحن',\n delivered: 'تم التوصيل'\n};\n\nأما مع Record — فهو أكثر إيجازًا ووضوحًا:
\n\ntype OrderStatus = 'pending' | 'shipped' | 'delivered';\n\nconst statusDescriptions: Record<OrderStatus, string> = {\n pending: 'قيد الانتظار',\n shipped: 'تم الشحن',\n delivered: 'تم التوصيل'\n};\n\nالميزة: إذا حاولت إضافة cancelled إلى الكائن، سيشير TypeScript فورًا إلى الخطأ — هذه الحالة غير موجودة في union type.
أفضل 5 أمثلة عملية لاستخدام Record
\n\nلنرَ أين يُبسّط Record حياتنا حقًا.
\n\n1. إعدادات التطبيق
\nخزّن الإعدادات بمفاتيح معروفة ولكن بقيم مختلفة الأنواع:
\n\ntype ConfigKeys = 'apiUrl' | 'timeout' | 'retryCount';\ntype ConfigValue = string | number | boolean;\n\nconst appConfig: Record<ConfigKeys, ConfigValue> = {\n apiUrl: 'https://api.example.com',\n timeout: 5000,\n retryCount: 3\n};\n\n2. ربط التعدادات (enums) أو الثوابت
\nغالبًا ما تحتاج إلى ربط كود باسم قابل للقراءة البشرية:
\n\nenum HttpStatus {\n OK = 200,\n NotFound = 404,\n Error = 500\n}\n\nconst statusMessages: Record<HttpStatus, string> = {\n [HttpStatus.OK]: 'كل شيء بخير',\n [HttpStatus.NotFound]: 'غير موجود',\n [HttpStatus.Error]: 'خطأ في الخادم'\n};\n\n3. النماذج والتحقق من الصحة
\nRecord مثالي لتخزين أخطاء التحقق حسب الحقول:
\n\ntype FormFields = 'email' | 'password' | 'name';\ntype ValidationErrors = Record<FormFields, string | null>;\n\nconst errors: ValidationErrors = {\n email: 'بريد إلكتروني غير صالح',\n password: null,\n name: 'الاسم مطلوب'\n};\n\n4. تخزين مؤقت للبيانات (Cache)
\nمحاكاة ذاكرة تخزين مؤقت بمفاتيح معرفة:
\n\ntype UserI