تخطي إلى المحتوى الرئيسي

الأنواع الشرطية (Conditional Types) وتعيين الأنواع (Mapped Types)في TypeScript

الأنواع الشرطية (Conditional Types) وتعيين الأنواع (Mapped Types)في TypeScript

مقدمة عن الأنواع الشرطية والمُزينات

تعريف الأنواع الشرطية والمُزينات وأهميتهما في TypeScript

في هذا القسم، سنتناول مفهوم الأنواع الشرطية والمُزينات وأهميتهما في لغة البرمجة TypeScript. تعتبر TypeScript توسعة للغة البرمجة JavaScript وتوفر ميزات إضافية لتعزيز تطوير البرمجيات.

الأنواع الشرطية (Conditional Types) هي نوع خاص من الأنواع في TypeScript تستخدم لتحديد نوع متغير بناءً على شرط معين. يمكن للأنواع الشرطية التعامل مع أنواع البيانات المعقدة وتوفير طريقة مرنة لتعريف الأنواع والتحقق من صحة البيانات.

من ناحية أخرى، تمثل المُزينات (Decorators) أداة قوية تسمح بتعديل الشفرة البرمجية أثناء مرحلة الترجمة والتشغيل. يمكن استخدام المُزينات لإضافة تعليقات توضيحية، تعريف خصائص وأساليب جديدة للكائنات، وتعديل سلوك الدوال والفئات.

في هذا القسم، سنستكشف الأنواع الشرطية والمُزينات بشكل مفصل ونتعلم كيفية استخدامهما لتحسين جودة الشفرة وتعزيز كفاءة تطوير البرمجيات.

الأنواع الشرطية (Conditional Types)

تتضمن TypeScript عدة أنواع شرطية تساعد في تعريف الأنواع بناءً على شرط معين. فيما يلي بعض الأمثلة على الأنواع الشرطية:

  • القيم البدائية (Primitive Values): تحدد هذه الأنواع الشرطية القيم البدائية مثل الأعداد الصحيحة والأعداد العشرية والنصوص.
  • القيم المعقدة (Complex Values): تحدد هذه الأنواع الشرطية القيم المعقدة مثل الكائنات والمصفوفات والقيم متعددة الأبعاد.
  • الأنواع الشرطية الخاصة (TypeScript-Specific Conditional Types): توفر TypeScript عدة أنواع شرطية خاصة تساعد في تعريف الأنواع بناءً على ظروف معينة.

يمكن استخدام الأنواع الشرطية لتحسين جودة الشفرة البرمجية وتعزيز فهم الكود للمطورين.

المُزينات (Decorators)

تُستخدم المُزينات لتعديل سلوك الدوال والفئات والكائنات خلال مرحلة الترجمة والتنفيذ. فيما يلي بعض الأمثلة على استخدام المُزينات:

  • تعريف الخصائص (Define Properties): تسمح المُزينات بتعريف خصائص جديدة للفئات دون تعديل الكود الأصلي.
  • تعديل السلوك (Modify Behavior): يمكن للمُزينات تعديل سلوك الدوال والفئات بطرق مختلفة كالتسجيل والتحقق.
  • تعريف المُزينات الخاصة (TypeScript-Specific Decorators): توفر TypeScript عدة مُزينات خاصة يمكن استخدامها لتحسين جودة الشفرة البرمجية وضبط سلوك الكائنات والدوال.

تلعب المُزينات دورًا مهمًا في تعزيز كفاءة تطوير البرمجيات وتحسين جودة الشفرة البرمجية.

استخدام الأنواع الشرطية والمُزينات في TypeScript

في TypeScript، يمكنك استخدام الأنواع الشرطية والمُزينات معًا لتحسين جودة الشفرة البرمجية وتعزيز فهم الكود. يمكن استخدام الأنواع الشرطية لتعريف أنواع محددة بناءً على شروط معينة، وكذلك يمكن للمُزينات تعديل سلوك الكائنات والدوال.

في الأقسام التالية، سنستكشف كيفية استخدام الأنواع الشرطية والمُزينات في TypeScript لتحسين جودة الشفرة البرمجية وتعزيز فهم الكود للمطورين.

أنواع شرطية خاصة في TypeScript

في هذا القسم، سنتناول بعض الأمثلة على الأنواع الشرطية الخاصة المتوفرة في TypeScript. يمكنك استخدامها لتعريف أنواع معقدة بناءً على شروط معينة.

نوع شرطي خاصشرح النوع
isStringتُستخدم النوع الشرطي isString للتحقق مما إذا كان نوع القيمة المعطاة عبارة عن سلسلة نصية.
isArrayتُستخدم النوع الشرطي isArray للتحقق مما إذا كان نوع القيمة المعطاة عبارة عن مصفوفة.
isObjectتُستخدم النوع الشرطي isObject للتحقق مما إذا كان نوع القيمة المعطاة عبارة عن كائن.

استخدام الأنواع الشرطية في TypeScript

في هذا القسم، سنستكشف كيفية استخدام الأنواع الشرطية في TypeScript لتحسين جودة الشفرة البرمجية وضمان سلامة الأنواع.

مثال عملي على استخدام الأنواع الشرطية

لنأخذ مثالاً عملياً لفهم كيفية تطبيق الأنواع الشرطية في TypeScript:

  • اكتب دالة تأخذ معامل من نوع ISomeEntity وتتعامل مع بيانات من نوع ISomeEntity.
  • استخدم النوع الشرطي isSomeEntity للتحقق من نوع القيمة المُمررة للدالة.
  • تحديد سلوك الدالة بناءً على نوع المعامل.

استخدام المزينات في TypeScript

في هذا القسم، سنستكشف كيفية استخدام المزينات في TypeScript لتحسين جودة الشفرة البرمجية وضمان سلامة الأنواع.

مثال عملي على استخدام المزينات

لنأخذ مثالاً عملياً لفهم كيفية تطبيق المزينات في TypeScript:

  • اكتب دالة أو فئة تستخدم المزينة @Decorator لتعريف سلوك الدالة.
  • استخدم المزينة @Decorator لإضافة تعليق توضيحي للدالة أو تسجيل استدعائها.
  • استخدم المزينة @Decorator لتعديل سلوك الدالة بناءً على قيم معينة.

أمثلة على استخدام الأنواع الشرطية والمزينات في TypeScript

في هذا القسم، سنقدم بعض الأمثلة العملية لتوضيح كيفية استخدام الأنواع الشرطية والمزينات في TypeScript:

  • اكتب دالة تأخذ معامل من النوع ISomeEntity وتتعامل مع قيم من نوع ISomeEntity.
  • استخدم النوع الشرطي isSomeEntity للتحقق من نوع المعامل المُمرر للدالة.
  • حدد سلوك الدالة بناءً على نوع الكيان المعطى.
interface ISomeEntity {
    id: string;
    title: string;
}

type CheckEntity<T> = T extends ISomeEntity ? true : false;

function processEntity<T>(entity: T): CheckEntity<T> {
    return (typeof entity === "object" && entity !== null && "id" in entity) as CheckEntity<T>;
}

// مثال على مزينة الفئة (Class Decorator)
function LogClass(target: Function) {
    console.log(`تم تسجيل الفئة: ${target.name}`);
}

@LogClass
class ArticleService {
    title: string = "كود التطور";
}

درس 25: الأنواع الشرطية (Conditional Types) في TypeScript

النوع الشرطي (Conditional Type) والأنواع الشرطية الأخرى في TypeScript

في هذا القسم، سنتعرف على مفهوم الأنواع الشرطية (Conditional Type) في TypeScript ونشرح الأنواع الشرطية المختلفة.

أنواع الشروط (Conditional Types):

تُستخدم الأنواع الشرطية لتعريف أنواع جديدة تعتمد على شروط معينة. في هذا السياق، تُستخدم الأنواع الشرطية لتحديد الأنواع بناءً على خصائص معينة.

أنواع الشروط الأساسية:

  • نوع “nullish” يُمثل القيم الفارغة والغير فارغة (null | undefined).
  • نوع “boolean” يُمثل القيم المنطقية.
  • نوع “string” يُمثل القيم النصية.
  • نوع “number” يُمثل القيم الرقمية.
  • نوع “bigint” يُمثل القيم الكبيرة.

أنواع الشروط واستخدام الأنواع المساعدة:

تُستخدم أنواع الشروط للتحقق من خصائص معينة في الأنواع وتوسيعها.

نوع “Extract”:

يُستخدم نوع “Extract” لاستخراج جزء معين من نوع بناءً على شرط معين ومطابقته:

type FruitNames = 'apple' | 'banana' | 'kiwi' | 'orange';
type YellowFruits = 'banana' | 'lemon';

type ExtractedFruit = Extract<FruitNames, YellowFruits>; // الناتج: 'banana'

نوع “Pick”:

يُستخدم نوع “Pick” لتحديد واقتطاع مجموعة خصائص محددة من كائن معين:

type Animal = {
    name: string;
    sound: string;
    age: number;
};

type AnimalSound = Pick<Animal, 'sound'>;

const animalSoundOnly: AnimalSound = { sound: 'bark' };
console.log(animalSoundOnly.sound); // الناتج: 'bark'

نوع “Omit”:

يُستخدم نوع “Omit” لإزالة خصائص معينة من كائن:

type AnimalWithoutAge = Omit<Animal, 'age'>;

const animalWithoutAge: AnimalWithoutAge = { name: 'dog', sound: 'bark' };
console.log(animalWithoutAge); // الناتج: { name: 'dog', sound: 'bark' }

نوع “Union Types” (أنواع الاتحاد):

تُستخدم الأنواع المتحدة لتحديد أنواع مختلفة بناءً على شرط معين:

type AnimalOrString = Animal | string;

const animalOrString: AnimalOrString = { name: 'dog', sound: 'bark', age: 3 };
console.log(typeof animalOrString); // الناتج: object

نوع “Intersection Types” (الأنواع المتقاطعة):

تُستخدم الأنواع المتقاطعة لدمج مجموعة من الخصائص والأنواع معاً باستخدام عامل &:

type Identifiable = { id: number };
type NamedAnimal = Animal & Identifiable;

const fullAnimal: NamedAnimal = {
    id: 1,
    name: 'dog',
    sound: 'bark',
    age: 3
};

أنواع الخرائط (Mapped Types) في TypeScript

مقدمة عن أنواع الخرائط

في هذا القسم، سنتعرف على مفهوم أنواع الخرائط وأنواعها المختلفة في لغة TypeScript. تعتبر أنواع الخرائط طريقة لتمثيل أنواع جديدة بناءً على أنواع موجودة. نستخدم أنواع الخرائط لتوسيع قدرات TypeScript وتحسين تجربة التطوير.

أنواع الخرائط الأساسية

  • نوع الخريطة (Map Type): يستخدم لإنشاء نوع جديد بناءً على نوع آخر بتكرار الخصائص.
  • نوع المجموعة الفرعية (Subtype Type): يستخدم لتأكيد أن قيمة ما تنتمي إلى نوع معين كالأعداد الصحيحة.

أنواع الخرائط المتقدمة

نوع الخريطة المتداخلة (Nested Map Type): يمكننا استخدام أنواع الخرائط لإنشاء أنواع معقدة تحتوي على أنواع أخرى:

type City = {
    name: string;
    street: string;
    distanceFromCenter: number;
};

نوع الشخص بخصائص متعددة:

type Person = {
    name: string;
    age: number;
    isAdult: boolean;
};

نوع الألوان:

type Colors = {
    red: string;
    green: string;
    blue: string;
};

نوع المجموعة المتداخلة لكائن الشخص مع العنوان:

type PersonWithAddress = {
    name: string;
    age: number;
    isAdult: boolean;
    address: {
        street: string;
        city: string;
        state: string;
    };
};

استخدام أنواع الخرائط والمجموعات المتداخلة

تمثل أنواع الخرائط والمجموعات القيم المحتملة لمجموعات الأنواع المختلفة:

  • Colors: نوع خرائط لتمثيل مجموعة من الألوان.
  • Numbers: نوع خرائط لتمثيل مجموعة الأعداد.
  • Strings: نوع خرائط لتمثيل مجموعة السلاسل النصية.
  • Person: نوع مجموعة لتمثيل الأشخاص.
  • DataWithLocation: نوع مجموعة متداخلة لتمثيل البيانات مع الموقع.
  • ParametersWithParamAndValue: نوع مجموعة متداخلة متداخلة لتمثيل الوسائط الوظيفية مع المعلمات والقيم.

استنتاج وتطبيقات الأنواع الشرطية وأنواع الخرائط

استخدام الأنواع الشرطية والمجموعات المتداخلة وأنواع الخرائط في TypeScript يوفر لك مرونة كبيرة في التعامل مع البيانات والتحكم في أنواع القيم المستخدمة في الكود الخاص بك لتحسين كفاءة البرمجيات وضمان سلامة البيانات.

أنواع القيم الشرطية المتقدمة (Advanced Conditional Value Types)

بعد أن استكشفنا الأساسيات القوية للأنواع الشرطية (Conditional Types) وكيفية استخدامها لاتخاذ قرارات نوعية بسيطة بناءً على علاقات التعيين (assignability)، حان الوقت للتعمق في آفاقها الأكثر تطوراً. في هذا القسم، سنتناول الجوانب المتقدمة لأنواع القيم الشرطية، والتي تتيح لنا بناء أنظمة أنواع معقدة ومرنة بشكل لا يصدق، قادرة على التعامل مع السيناريوهات البرمجية الأكثر تعقيداً.

تكمن قوة الأنواع الشرطية المتقدمة في قدرتها على دمج مفاهيم مثل الاستدلال النوعي (Type Inference) باستخدام الكلمة المفتاحية infer بطرق مبتكرة، ومعالجة الأنواع الاتحادية (Union Types) بذكاء، واستخدام التكرار (Recursion) لفك تغليف الأنواع المتداخلة أو المعقدة. هذه التقنيات تمكننا من إنشاء أنواع مساعدة (Utility Types) قوية ومخصصة تتجاوز بكثير ما توفره TypeScript افتراضياً.

الاستدلال النوعي المتقدم باستخدام infer

تُعد القدرة على استخلاص أنواع معلمات دالة معينة (parameters) ونوع القيمة التي تُرجعها (return type) من أقوى استخدامات infer:

type ParametersType<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never;

function greet(name: string, age: number): string {
    return `Hello ${name}, you are ${age} years old.`;
}

type GreetParams = ParametersType<typeof greet>; // [name: string, age: number]
type GreetReturn = ReturnType<typeof greet>;     // string

// مثال مع دوال غير متزامنة
type AsyncFunction = (id: number) => Promise<{ data: string }>;

type AsyncParams = ParametersType<AsyncFunction>; // [id: number]
type AsyncReturn = ReturnType<AsyncFunction>;     // Promise<{ data: string }>

هنا، ParametersType تستخلص نوع مجموعة المعلمات كـ tuple، و ReturnType تستخلص نوع القيمة المُرجعة. هذه الأنواع المساعدة تُستخدم داخلياً في TypeScript كجزء من الأنواع المساعدة الافتراضية Parameters<T> و ReturnType<T>.

استخلاص نوع العنصر من نوع مصفوفة أو Promise

type ArrayElementType<T> = T extends (infer E)[] ? E : never;
type PromiseResolvedType<T> = T extends Promise<infer R> ? R : never;

type StringArray = string[];
type NumberPromise = Promise<number>;

type StringElement = ArrayElementType<StringArray>;       // string
type NumberResolved = PromiseResolvedType<NumberPromise>; // number

type User = { id: number; name: string; };
type UsersPromise = Promise<User[]>;
type ResolvedUsers = PromiseResolvedType<UsersPromise>; // User[]
type ResolvedUserElement = ArrayElementType<ResolvedUsers>; // User

الأنواع الشرطية التوزيعية (Distributive Conditional Types)

من السمات القوية والمدهشة للأنواع الشرطية هي سلوكها “التوزيعي” عندما تُطبق على أنواع اتحادية (Union Types). إذا كان النوع T في T extends U ? X : Y نوعاً اتحادياً، فإن النوع الشرطي يُطبق على كل عضو بشكل منفصل ثم تُجمع النتائج:

type ToArray<T> = T extends any ? T[] : never;

type NumberOrString = number | string;
type NumberOrStringArray = ToArray<NumberOrString>; // number[] | string[]

// كيف يعمل نوع Exclude المساعد:
type MyExclude<T, U> = T extends U ? never : T;

type MixedUnion = 'a' | 'b' | 1 | 2;
type ExcludedUnion = MyExclude<MixedUnion, number>; // 'a' | 'b'

أنواع الخرائط الشرطية (Conditional Mapped Types)

بعد أن استكشفنا القوة الكامنة في الأنواع الشرطية (Conditional Types) وقدرة أنواع الخرائط (Mapped Types) على تحويل بنية الأنواع، حان الوقت لنجمع بين هاتين الميزتين الرائعتين في مفهوم واحد أكثر تطوراً ومرونة: أنواع الخرائط الشرطية (Conditional Mapped Types).

جوهر أنواع الخرائط الشرطية وإعادة تعيين المفاتيح (Key Remapping)

الميزة الأبرز التي تميز أنواع الخرائط الشرطية هي استخدام إعادة تعيين المفاتيح (Key Remapping) جنباً إلى جنب مع الأنواع الشرطية عبر الكلمة المفتاحية as لتصفية المفاتيح أو تعديل أسمائها:

type ConditionalMappedType<T> = {
    [K in keyof T as T[K] extends Function ? K : never]: T[K]
};

أنواع الخرائط الشرطية المتقدمة (Advanced Conditional Mapped Types)

تكمن القوة الحقيقية للأنواع الشرطية المدمجة مع أنواع الخرائط في قدرتها على إجراء عمليات تحويل ديناميكية للخصائص، ليس فقط بناءً على وجودها أو غيابها، بل بناءً على طبيعة قيمها، أو حتى استخلاص أجزاء معينة من أنواعها.

استخلاص الأنواع باستخدام infer ضمن أنواع الخرائط الشرطية

type FunctionReturnType<T> = {
    [P in keyof T]: T[P] extends (...args: any[]) => infer R ? R : T[P];
};

interface UserActions {
    getName(): string;
    getAge(): number;
    isActive: boolean;
    getAddress(id: number): Promise<string>;
}

type UserActionReturnTypes = FunctionReturnType<UserActions>;
// النتيجة:
// {
//    getName: string;
//    getAge: number;
//    isActive: boolean;
//    getAddress: Promise<string>;
// }

الأنواع الخرائطية الشرطية المتكررة (Recursive Conditional Mapped Types)

لتحويل جميع الخصائص المتداخلة داخل الكائنات إلى readonly بعمق، نستخدم التكرار البرمجي (Recursion):

type DeepReadonly<T> = {
    readonly [P in keyof T]: T[P] extends object
        ? T[P] extends Function
            ? T[P]
            : DeepReadonly<T[P]>
        : T[P];
};

interface Address {
    street: string;
    city: string;
}

interface UserProfile {
    id: number;
    name: string;
    contact: {
        email: string;
        phone: string;
    };
    homeAddress: Address;
    logActivity: (message: string) => void;
}

type ImmutableProfile = DeepReadonly<UserProfile>;

أمثلة وتطبيقات عملية

1. استخلاص نوع القيمة المحللة من الوعد (Promise)

في عالم JavaScript الحديث، أصبحت الوعود (Promises) والوظائف غير المتزامنة (async/await) جزءًا لا يتجزأ من التعامل مع العمليات. نستخدم نوعاً مساعداً يسمى UnwrapPromise لاستخلاص النوع المحتوى داخل الوعود:

/**
 * يستخلص نوع القيمة المحللة من الوعد (Promise).
 * إذا كان النوع T هو Promise، فإنه يعيد نوع القيمة المحللة (U).
 * وإلا، فإنه يعيد النوع T نفسه.
 */
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

// 1. مع وعد غير متزامن
const fetchData = (): Promise<{ id: number; name: string }> => {
    return Promise.resolve({ id: 1, name: 'Example Data' });
};

type FetchDataType = UnwrapPromise<ReturnType<typeof fetchData>>;
// FetchDataType: { id: number; name: string }

// 2. مع دالة متزامنة (لا تعيد Promise)
const getStaticName = (): string => "محمد";
type StaticNameType = UnwrapPromise<ReturnType<typeof getStaticName>>;
// StaticNameType: string (يعود النوع نفسه مباشرة دون تعديل)

المراجع والمصادر

للمزيد من التعلم والاستكشاف حول الأنواع الشرطية والمجموعات المتداخلة في TypeScript:


اكتشاف المزيد من كود التطور

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

MOHAMMAD ABU KASHREEF

محمد، مهندس برمجيات وصانع محتوى تقني، مؤسس كود التطور. أشارك خبرتي في البرمجة والتقنية بأسلوب عملي وتجارب واقعية، بهدف تبسيط المعرفة وبناء محتوى عربي موثوق وقابل للتطبيق.

اترك رد