أنماط الأنواع العامة المتقدمة
الآن بعد أن فهمت أساسيات الأنواع العامة، دعنا نستكشف أنماط الأنواع العامة المتقدمة التي تستفيد من نظام الأنواع القوي في TypeScript. هذه الأنماط تمكّن من معالجات أنواع متطورة تجعل كودك أكثر تعبيراً وقابلية للصيانة.
الأنواع الشرطية (Conditional Types)
الأنواع الشرطية تسمح لك باختيار أنواع مختلفة بناءً على شرط. تتبع نمط المشغل الثلاثي: T extends U ? X : Y
// نوع شرطي أساسي
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
// نوع شرطي لاستخراج أنواع الإرجاع
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser(): { name: string; age: number } {
return { name: "Alice", age: 30 };
}
type UserType = ReturnType<typeof getUser>; // { name: string; age: number }
// نوع شرطي لعناصر المصفوفة
type ElementType<T> = T extends (infer E)[] ? E : T;
type StringArray = ElementType<string[]>; // string
type NumberType = ElementType<number>; // number
// أنواع شرطية متداخلة
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T1 = TypeName<string>; // "string"
type T2 = TypeName<() => void>; // "function"
ملاحظة: الأنواع الشرطية قوية بشكل خاص عند دمجها مع الكلمة المفتاحية infer، والتي تسمح لك باستخراج الأنواع من أنواع أخرى أثناء عملية فحص الأنواع.
الكلمة المفتاحية infer
الكلمة المفتاحية infer تتيح لك استخراج وتسمية نوع داخل نوع شرطي. فكر فيه كمطابقة الأنماط للأنواع:
// استخراج نوع الإرجاع من الدالة
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function add(a: number, b: number): number {
return a + b;
}
type AddReturn = GetReturnType<typeof add>; // number
// استخراج أنواع المعاملات
type GetFirstParam<T> = T extends (first: infer F, ...args: any[]) => any
? F
: never;
type FirstParam = GetFirstParam<typeof add>; // number
// استخراج جميع المعاملات كمجموعة
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type AddParams = Parameters<typeof add>; // [a: number, b: number]
// استخراج نوع العنصر من Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type AsyncUser = UnwrapPromise<Promise<{ name: string }>>; // { name: string }
type SyncUser = UnwrapPromise<{ name: string }>; // { name: string }
// فك Promise بشكل عميق
type DeepUnwrap<T> = T extends Promise<infer U>
? DeepUnwrap<U>
: T;
type Nested = DeepUnwrap<Promise<Promise<Promise<string>>>>; // string
// استخراج نوع المثيل من المُنشئ
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never;
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
type UserInstance = InstanceType<typeof User>; // User
الأنواع الشرطية التوزيعية (Distributive Conditional Types)
عندما تعمل الأنواع الشرطية على أنواع الاتحاد، فإنها تتوزع على كل عضو في الاتحاد:
// نوع شرطي توزيعي
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumber = string | number;
type Arrays = ToArray<StringOrNumber>; // string[] | number[]
// نسخة غير توزيعية (باستخدام حيلة المجموعة)
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type SingleArray = ToArrayNonDist<StringOrNumber>; // (string | number)[]
// تصفية null و undefined من الاتحاد
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string
// استخراج أنواع محددة من الاتحاد
type ExtractStrings<T> = T extends string ? T : never;
type Mixed = string | number | boolean | string[];
type OnlyStrings = ExtractStrings<Mixed>; // string
// استبعاد أنواع محددة من الاتحاد
type ExcludeNumbers<T> = T extends number ? never : T;
type WithoutNumbers = ExcludeNumbers<Mixed>; // string | boolean | string[]
نصيحة: لمنع التوزيع، غلف معامل النوع في مجموعة: [T] extends [U] بدلاً من T extends U. هذا مفيد عندما تريد معاملة أنواع الاتحاد كوحدة واحدة.
الأنواع المعينة (Mapped Types)
الأنواع المعينة تحول كل خاصية في نوع. إنها مثالية لإنشاء اختلافات من الأنواع الموجودة:
// نوع معين أساسي - جعل جميع الخصائص اختيارية
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string }
// جعل جميع الخصائص مطلوبة
type Required<T> = {
[P in keyof T]-?: T[P];
};
type RequiredUser = Required<PartialUser>;
// { id: number; name: string; email: string }
// جعل جميع الخصائص للقراءة فقط
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string }
// اختيار خصائص محددة
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type UserNameAndEmail = Pick<User, "name" | "email">;
// { name: string; email: string }
// حذف خصائص محددة
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type UserWithoutId = Omit<User, "id">;
// { name: string; email: string }
// إنشاء نوع سجل
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type UserRoles = Record<"admin" | "user" | "guest", boolean>;
// { admin: boolean; user: boolean; guest: boolean }
أنماط الأنواع المعينة المتقدمة
ادمج الأنواع المعينة مع الأنواع الشرطية لتحولات قوية:
// جعل الخصائص قابلة للإلغاء
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type NullableUser = Nullable<User>;
// { id: number | null; name: string | null; email: string | null }
// تحويل خصائص الدالة إلى أنواع إرجاعها
type FunctionPropertyTypes<T> = {
[P in keyof T]: T[P] extends Function ? ReturnType<T[P]> : T[P];
};
interface Actions {
getName: () => string;
getAge: () => number;
isActive: boolean;
}
type ActionResults = FunctionPropertyTypes<Actions>;
// { getName: string; getAge: number; isActive: boolean }
// للقراءة فقط بشكل عميق
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
interface Company {
name: string;
address: {
street: string;
city: string;
};
}
type ReadonlyCompany = DeepReadonly<Company>;
// {
// readonly name: string;
// readonly address: {
// readonly street: string;
// readonly city: string;
// }
// }
// نوع Getters - تحويل الخصائص إلى دوال getter
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
type UserGetters = Getters<User>;
// {
// getId: () => number;
// getName: () => string;
// getEmail: () => string;
// }
تحذير: الأنواع التكرارية العميقة يمكن أن تتسبب في وصول TypeScript إلى حدود التكرار. للهياكل المتداخلة بعمق، فكر في تحديد عمق أقصى أو استخدام معدّل -? للتعامل مع الخصائص الاختيارية بعناية.
أنواع القوالب النصية (Template Literal Types)
أنواع القوالب النصية تسمح لك بمعالجة أنواع النصوص الحرفية باستخدام بناء جملة القوالب:
// نوع قالب نصي أساسي
type Greeting<T extends string> = `Hello ${T}`;
type HelloWorld = Greeting<"World">; // "Hello World"
// نمط تسمية الأحداث
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type ChangeEvent = EventName<"change">; // "onChange"
// توليد معالجات أحداث متعددة
type Events = "click" | "hover" | "focus";
type EventHandlers = {
[E in Events as `on${Capitalize<E>}`]: (event: Event) => void;
};
// {
// onClick: (event: Event) => void;
// onHover: (event: Event) => void;
// onFocus: (event: Event) => void;
// }
// تسمية خاصية CSS
type CSSProperty<T extends string> = `--${T}`;
type ThemeColors = "primary" | "secondary" | "accent";
type CSSVariables = {
[C in ThemeColors as CSSProperty<C>]: string;
};
// {
// "--primary": string;
// "--secondary": string;
// "--accent": string;
// }
// دمج نصوص حرفية متعددة
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = "users" | "posts" | "comments";
type APIRoute = `/${Endpoint}`;
type APICall = `${HTTPMethod} ${APIRoute}`;
type UserRoutes = Extract<APICall, `${string}users`>;
// "GET /users" | "POST /users" | "PUT /users" | "DELETE /users"
// مطابقة الأنماط مع القوالب النصية
type ExtractParam<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParam<`/${Rest}`>
: T extends `${string}:${infer Param}`
? Param
: never;
type Route = "/users/:userId/posts/:postId";
type Params = ExtractParam<Route>; // "userId" | "postId"
إعادة تعيين المفاتيح في الأنواع المعينة
TypeScript يسمح لك بإعادة تعيين المفاتيح أثناء إنشاء الأنواع المعينة باستخدام بند as:
// إزالة خصائص محددة بإعادة التعيين إلى never
type RemoveKindField<T> = {
[P in keyof T as Exclude<P, "kind">]: T[P];
};
interface Circle {
kind: "circle";
radius: number;
}
type CircleWithoutKind = RemoveKindField<Circle>; // { radius: number }
// بادئة أسماء الخصائص
type Prefixed<T, P extends string> = {
[K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};
type PrefixedUser = Prefixed<User, "user">;
// { userId: number; userName: string; userEmail: string }
// تصفية الخصائص حسب النوع
type PickByType<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
interface Mixed {
name: string;
age: number;
active: boolean;
email: string;
}
type StringProps = PickByType<Mixed, string>; // { name: string; email: string }
type NumberProps = PickByType<Mixed, number>; // { age: number }
// إنشاء اتحاد متمايز من الخصائص
type Discriminate<T, K extends keyof T> = {
[P in keyof T]: { type: P; value: T[P] };
}[keyof T];
type Config = {
string: string;
number: number;
boolean: boolean;
};
type ConfigValue = Discriminate<Config, "string" | "number" | "boolean">;
// { type: "string"; value: string } |
// { type: "number"; value: number } |
// { type: "boolean"; value: boolean }
الأنواع الشرطية التكرارية
TypeScript يدعم تعريفات الأنواع التكرارية، مما يتيح تحولات أنواع معقدة:
// نوع تكراري لتسطيح المصفوفات المتداخلة
type Flatten<T> = T extends Array<infer U>
? U extends Array<any>
? Flatten<U>
: U
: T;
type Nested = [[[string]], number];
type Flat = Flatten<Nested>; // string | number
// نوع تكراري لـ partial عميق
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
interface NestedConfig {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
cache: {
enabled: boolean;
ttl: number;
};
}
type PartialConfig = DeepPartial<NestedConfig>;
// جميع الخصائص على جميع المستويات اختيارية
// نوع تكراري لنصوص المسارات
type PathKeys<T, Prefix extends string = ""> = {
[K in keyof T]: T[K] extends object
? K extends string
? `${Prefix}${K}` | PathKeys<T[K], `${Prefix}${K}.`>
: never
: K extends string
? `${Prefix}${K}`
: never;
}[keyof T];
type ConfigPaths = PathKeys<NestedConfig>;
// "database" | "database.host" | "database.port" |
// "database.credentials" | "database.credentials.username" | ...
// الحصول على نوع القيمة عن طريق المسار
type PathValue<T, P extends string> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? PathValue<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
type HostType = PathValue<NestedConfig, "database.host">; // string
type PortType = PathValue<NestedConfig, "database.port">; // number
ملاحظة: TypeScript 4.5+ زادت حد التكرار للأنواع الشرطية إلى 1000، ولكن كن حذراً من آثار الأداء مع الأنواع التكرارية العميقة. استخدمها بحكمة في كود الإنتاج.
أنماط الأنواع العامة المتقدمة العملية
لنجمع هذه المفاهيم في أنواع أدوات في العالم الحقيقي:
// مُصدر أحداث آمن للأنواع
type EventMap = {
[event: string]: any;
};
class TypedEventEmitter<T extends EventMap> {
private listeners: { [K in keyof T]?: Array<(data: T[K]) => void> } = {};
on<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(listener);
}
emit<K extends keyof T>(event: K, data: T[K]): void {
this.listeners[event]?.forEach(listener => listener(data));
}
}
// الاستخدام
interface MyEvents {
login: { userId: number; timestamp: Date };
logout: { userId: number };
error: { message: string; code: number };
}
const emitter = new TypedEventEmitter<MyEvents>();
emitter.on("login", (data) => {
console.log(data.userId); // آمن للأنواع: userId هو number
console.log(data.timestamp); // آمن للأنواع: timestamp هو Date
});
emitter.emit("login", { userId: 1, timestamp: new Date() }); // موافق
// emitter.emit("login", { userId: "1" }); // خطأ: نوع خاطئ
// نمط Builder مع تراكم الأنواع
type Builder<T> = {
[K in keyof T]-?: (value: T[K]) => Builder<T> & { build(): T };
};
function createBuilder<T>(): Builder<T> {
const data: any = {};
const builder: any = {};
return new Proxy(builder, {
get(_, prop) {
if (prop === "build") {
return () => data;
}
return (value: any) => {
data[prop] = value;
return builder;
};
}
});
}
interface Person {
name: string;
age: number;
email: string;
}
const person = createBuilder<Person>()
.name("Alice")
.age(30)
.email("alice@example.com")
.build(); // النوع: Person
تمرين: أنشئ منشئ استعلام آمن للأنواع ينشئ استعلامات شبيهة بـ SQL. عرّف نوع Query<T> بطرق select<K extends keyof T>(...keys: K[]), where(condition: Partial<T>), و orderBy<K extends keyof T>(key: K, direction: "asc" | "desc"). استخدم الأنواع الشرطية لتتبع الطرق التي تم استدعاؤها ومنع بناء استعلام غير صالح.
الخلاصة
الأنواع العامة المتقدمة تفتح قدرات نظام الأنواع الكاملة في TypeScript. لقد تعلمت الأنواع الشرطية لمنطق مستوى النوع، والكلمة المفتاحية infer لاستخراج الأنواع، والأنواع المعينة لتحويل أنواع الكائنات، وأنواع القوالب النصية لمعالجة النصوص، والأنواع التكرارية للتحولات العميقة. هذه الأنماط هي أساس مكتبات وأطر TypeScript المتطورة، مما يتيح سلامة أنواع استثنائية وتجربة مطور رائعة.