🚀 احتراف الـ Mapped Types في TypeScript: حوّل أنواعك بذكاء!

اليوم سنتحدث عن مفهوم قوي جداً في لغة TypeScript يسمى Mapped Types.

إذا كنت قد تعبت من تكرار كتابة الخصائص (Properties) في أكثر من Type، أو كنت ترغب في تغيير حالة مجموعة من الخصائص (مثل جعلها جميعاً اختيارية) دفعة واحدة، فإن الـ Mapped Types هي الحل السحري لك.

ما هي الـ Mapped Types ببساطة؟ 🤔

تخيل أن لديك "قالب" (Type) معين، وتريد إنشاء "قالب جديد" يشبهه تماماً ولكن مع تعديل بسيط في كل خاصية فيه. بدلاً من كتابة النوع الجديد يدوياً، تتيح لك TypeScript عمل "حلقة تكرار" (Loop) على خصائص النوع الأول لإنشاء النوع الثاني.

بكلمات بسيطة: هي طريقة لإنشاء نوع جديد عن طريق "رسم خريطة" (Mapping) لخصائص نوع موجود مسبقاً.


🛠️ كيف تعمل الـ Mapped Types؟ (الصيغة الأساسية)

لكي نستخدم Mapped Types، نحتاج إلى استخدام الكلمة المفتاحية in التي تعمل مثل حلقة for...in في الجافاسكريبت، ولكنها تعمل هنا على مستوى "الأنواع" وليس "القيم".

لنلقِ نظرة على المثال التالي:

لنفترض أن لدينا نوعاً يمثل بيانات مستخدم بسيط:

type User = {
    name: string;
    age: number;
};

الآن، نريد إنشاء نوع جديد يجعل كل الخصائص في User من نوع string (مثلاً لغرض عرض البيانات في واجهة المستخدم)، بدلاً من تعريف النوع يدوياً مرة أخرى:

// هنا نقوم بإنشاء Mapped Type
type UserStrings<T> = {
    [Property in keyof T]: string; // لكل خاصية موجودة في T، اجعل نوعها string
};

// تطبيق النوع على User
type UserAsString = UserStrings<User>;

/* 
النتيجة النهائية لـ UserAsString ستكون:
{
    name: string;
    age: string;
}
*/

شرح الكود خطوة بخطوة:

  1. keyof T: تعطينا جميع المفاتيح (Keys) الموجودة في النوع T (في حالتنا هي name و age).
  2. [Property in keyof T]: هذه هي "الحلقة"، فهي تمر على كل مفتاح واحد تلو الآخر.
  3. : string: تخبر TypeScript أن كل هذه المفاتيح يجب أن يكون نوع قيمتها هو string.

⚡️ التحكم في الخصائص: جعلها اختيارية أو للقراءة فقط

من أقوى مميزات Mapped Types هي قدرتها على إضافة "معدّلات" (Modifiers) للخصائص. أشهر هذه المعدّلات هي:

  • ?: لجعل الخاصية اختيارية (Optional).
  • readonly: لجعل الخاصية للقراءة فقط.

1. تحويل الخصائص إلى "اختيارية" (Optional) ❓

لنفرض أنك تريد إنشاء نوع يستخدم لعمل "تحديث" (Update) لبيانات المستخدم، حيث لا يشترط إرسال كل البيانات، بل بعضها فقط.

type User = {
    name: string;
    age: number;
};

// Mapped Type لجعل كل الخصائص اختيارية
type OptionalUser<T> = {
    [Property in keyof T]?: T[Property]; // علامة ? تجعل الخاصية اختيارية
};

type UserUpdate = OptionalUser<User>;

/* 
النتيجة النهائية لـ UserUpdate ستكون:
{
    name?: string;
    age?: number;
}
*/

ملاحظة: استخدمنا T[Property] للحفاظ على النوع الأصلي للخاصية (الاسم يبقى string والعمر يبقى number).

2. تحويل الخصائص إلى "للقراءة فقط" (Readonly) 🔒

إذا كنت تريد إنشاء نسخة من البيانات لا يمكن تعديلها بعد إنشائها:

type User = {
    name: string;
    age: number;
};

// Mapped Type لجعل كل الخصائص للقراءة فقط
type ReadonlyUser<T> = {
    readonly [Property in keyof T]: T[Property];
};

type LockedUser = ReadonlyUser<User>;

/* 
النتيجة النهائية لـ LockedUser ستكون:
{
    readonly name: string;
    readonly age: number;
}
*/

💡 ملخص سريع لما تعلمناه

الميزة الفائدة الطريقة
Mapped Types إنشاء أنواع بناءً على أنواع أخرى استخدام [K in keyof T]
تغيير النوع توحيد نوع كل الخصائص تحديد نوع ثابت بعد النقطتين :
الخصائص الاختيارية جعل الحقول غير إلزامية إضافة ? قبل النقطتين :
القراءة فقط منع تعديل القيم إضافة readonly قبل اسم الخاصية