الأنواع المتقدمة
الأنواع المتقدمة في TypeScript
يوفر TypeScript مجموعة غنية من ميزات الأنواع المتقدمة التي تسمح لك بالتعبير عن علاقات الأنواع المعقدة وإنشاء كود آمن للغاية من حيث النوع. في هذا الدرس، سنستكشف عوامل ونماذج الأنواع المتقدمة التي ستأخذ مهاراتك في TypeScript إلى المستوى التالي.
معامل keyof
يأخذ معامل keyof نوع كائن وينتج اتحاد حرفي من سلاسل أو أرقام لمفاتيحه. هذا مفيد بشكل لا يصدق لإنشاء أنماط وصول إلى الخصائص آمنة من حيث النوع.
<interface Person {
name: string;
age: number;
email: string;
}
// إنشاء نوع اتحاد لجميع المفاتيح
type PersonKeys = keyof Person;
// النتيجة: 'name' | 'age' | 'email'
// مثال عملي: دالة الحصول على الخاصية آمنة من حيث النوع
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person: Person = {
name: 'جون دو',
age: 30,
email: 'john@example.com'
};
const name = getProperty(person, 'name'); // النوع: string
const age = getProperty(person, 'age'); // النوع: number
// خطأ: نوع الوسيط 'invalid' غير قابل للتعيين
// const invalid = getProperty(person, 'invalid');
>
keyof ضروري لإنشاء دوال آمنة من حيث النوع تعمل مع خصائص الكائنات بشكل ديناميكي.
معامل typeof
يحصل معامل typeof على نوع القيمة. هذا مفيد بشكل خاص عندما تريد الإشارة إلى نوع قيمة موجودة دون تعريف نوع منفصل بشكل صريح.
<// الحصول على النوع من قيمة
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
headers: {
'Content-Type': 'application/json'
}
};
type Config = typeof config;
// النتيجة: {
// apiUrl: string;
// timeout: number;
// retries: number;
// headers: { 'Content-Type': string; };
// }
// الحصول على نوع الدالة
function createUser(name: string, email: string) {
return { name, email, id: Math.random() };
}
type CreateUserFn = typeof createUser;
// النتيجة: (name: string, email: string) => { name: string; email: string; id: number; }
// دمج مع ReturnType
type User = ReturnType<typeof createUser>;
// النتيجة: { name: string; email: string; id: number; }
// الحصول على قيم enum
const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT'
} as const;
type DirectionType = typeof Direction;
type DirectionValue = typeof Direction[keyof typeof Direction];
// النتيجة: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
>
أنواع الوصول المفهرس
تسمح لك أنواع الوصول المفهرس بالبحث عن خصائص محددة في نوع آخر باستخدام تدوين الأقواس. هذا مفيد لاستخراج نوع الخصائص المتداخلة.
<interface Company {
name: string;
address: {
street: string;
city: string;
country: string;
coordinates: {
lat: number;
lng: number;
};
};
employees: Array<{
name: string;
role: string;
salary: number;
}>;
}
// الوصول إلى أنواع الخصائص المتداخلة
type Address = Company['address'];
// النتيجة: { street: string; city: string; country: string; coordinates: {...}; }
type Coordinates = Company['address']['coordinates'];
// النتيجة: { lat: number; lng: number; }
type EmployeeArray = Company['employees'];
// النتيجة: Array<{ name: string; role: string; salary: number; }>
// الوصول إلى نوع عنصر المصفوفة
type Employee = Company['employees'][number];
// النتيجة: { name: string; role: string; salary: number; }
// الوصول إلى خاصية محددة لعنصر المصفوفة
type EmployeeRole = Company['employees'][number]['role'];
// النتيجة: string
// وصول مفهرس متعدد
type CityOrCountry = Company['address']['city' | 'country'];
// النتيجة: string
>
[number] للوصول إلى نوع العنصر لنوع المصفوفة.
أنواع القوالب الحرفية
تُبنى أنواع القوالب الحرفية على أنواع السلاسل الحرفية ويمكن أن تتوسع إلى العديد من السلاسل عبر الاتحادات. تسمح لك بإنشاء أنواع سلاسل جديدة بناءً على أنواع موجودة.
<// نوع قالب حرفي أساسي
type World = 'world';
type Greeting = `hello ${World}`;
// النتيجة: 'hello world'
// إنشاء أنواع نقاط نهاية API
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Resource = 'user' | 'post' | 'comment';
type Endpoint = `/${Lowercase<Resource>}`;
// النتيجة: '/user' | '/post' | '/comment'
type ApiRoute = `${HttpMethod} ${Endpoint}`;
// النتيجة: 'GET /user' | 'GET /post' | ... (12 مجموعة)
// أسماء الأحداث
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// النتيجة: 'onClick' | 'onFocus' | 'onBlur'
// خصائص CSS
type CSSProperty = 'color' | 'font-size' | 'border-width';
type CSSValue = string | number;
type StyleObject = {
[K in CSSProperty]: CSSValue;
};
// متقدم: إنشاء أنواع getter/setter
type PropName = 'name' | 'age' | 'email';
type Getters = {
[K in PropName as `get${Capitalize<K>}`]: () => string;
};
// النتيجة: { getName: () => string; getAge: () => string; getEmail: () => string; }
type Setters = {
[K in PropName as `set${Capitalize<K>}`]: (value: string) => void;
};
>
أنواع معالجة السلاسل الجوهرية
يوفر TypeScript أنواعًا مدمجة لمعالجات السلاسل الشائعة: Uppercase و Lowercase و Capitalize و Uncapitalize.
<type Greeting = 'hello world';
// تحويل إلى أحرف كبيرة
type LoudGreeting = Uppercase<Greeting>;
// النتيجة: 'HELLO WORLD'
// تحويل إلى أحرف صغيرة
type QuietGreeting = Lowercase<LoudGreeting>;
// النتيجة: 'hello world'
// تكبير الحرف الأول
type CapitalizedGreeting = Capitalize<Greeting>;
// النتيجة: 'Hello world'
// تصغير الحرف الأول
type UncapitalizedGreeting = Uncapitalize<CapitalizedGreeting>;
// النتيجة: 'hello world'
// مثال عملي: رؤوس HTTP
type HttpHeader = 'content-type' | 'authorization' | 'accept';
type HttpHeaderCapitalized = Capitalize<HttpHeader>;
// النتيجة: 'Content-type' | 'Authorization' | 'Accept'
// إنشاء ثوابت
type StatusCode = 'ok' | 'error' | 'pending';
type StatusConstant = `STATUS_${Uppercase<StatusCode>}`;
// النتيجة: 'STATUS_OK' | 'STATUS_ERROR' | 'STATUS_PENDING'
>
الأنواع التكرارية
الأنواع التكرارية تشير إلى نفسها في تعريفها. هذا قوي لنمذجة الهياكل المتداخلة أو الشبيهة بالأشجار.
<// نوع تكراري بسيط: JSON
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const validJSON: JSONValue = {
name: 'جون',
age: 30,
hobbies: ['القراءة', 'البرمجة'],
address: {
city: 'نيويورك',
coordinates: {
lat: 40.7128,
lng: -74.0060
}
}
};
// شجرة نظام الملفات
interface FileSystemNode {
name: string;
type: 'file' | 'directory';
children?: FileSystemNode[];
}
const fileSystem: FileSystemNode = {
name: 'root',
type: 'directory',
children: [
{
name: 'src',
type: 'directory',
children: [
{ name: 'index.ts', type: 'file' },
{ name: 'utils.ts', type: 'file' }
]
},
{ name: 'package.json', type: 'file' }
]
};
// مسارات الكائنات المتداخلة بعمق
type PathImpl<T, Key extends keyof T> =
Key extends string
? T[Key] extends Record<string, any>
? `${Key}.${PathImpl<T[Key], Exclude<keyof T[Key], keyof any[]>> & string}`
| `${Key}.${Exclude<keyof T[Key], keyof any[]> & string}`
: never
: never;
type Path<T> = PathImpl<T, keyof T> | keyof T;
interface DeepObject {
user: {
profile: {
name: string;
settings: {
theme: string;
};
};
};
}
type DeepPaths = Path<DeepObject>;
// النتيجة: 'user' | 'user.profile' | 'user.profile.name' | 'user.profile.settings' | ...
>
الأنواع الممثلة مع إعادة تسمية المفاتيح
تسمح لك إعادة تسمية المفاتيح بتحويل المفاتيح في الأنواع الممثلة باستخدام جملة as. هذا يمكّن تحويلات الأنواع المتطورة.
<interface Person {
name: string;
age: number;
email: string;
}
// إنشاء getters مع إعادة تسمية المفاتيح
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type PersonGetters = Getters<Person>;
// النتيجة: {
// getName: () => string;
// getAge: () => number;
// getEmail: () => string;
// }
// تصفية الخصائص حسب النوع
type FilterByType<T, ValueType> = {
[K in keyof T as T[K] extends ValueType ? K : never]: T[K];
};
type StringPropsOnly = FilterByType<Person, string>;
// النتيجة: { name: string; email: string; }
type NumberPropsOnly = FilterByType<Person, number>;
// النتيجة: { age: number; }
// إزالة بوادئ محددة
type RemovePrefix<T, Prefix extends string> = {
[K in keyof T as K extends `${Prefix}${infer Rest}` ? Rest : K]: T[K];
};
interface PrefixedData {
data_name: string;
data_age: number;
email: string;
}
type CleanedData = RemovePrefix<PrefixedData, 'data_'>;
// النتيجة: { name: string; age: number; email: string; }
>
الأنواع الشرطية مع الاستدلال
يمكن للأنواع الشرطية استدلال الأنواع داخل الشرط باستخدام الكلمة المفتاحية infer. هذا يمكّن أنماط استخراج الأنواع القوية.
<// استخراج نوع الإرجاع
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: 'جون' };
}
type UserType = GetReturnType<typeof getUser>;
// النتيجة: { id: number; name: string; }
// استخراج نوع عنصر المصفوفة
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type Numbers = ArrayElement<number[]>;
// النتيجة: number
type Users = ArrayElement<Array<{ id: number; name: string }>>;
// النتيجة: { id: number; name: string; }
// استخراج نوع قيمة Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type AsyncResult = UnwrapPromise<Promise<string>>;
// النتيجة: string
type SyncResult = UnwrapPromise<number>;
// النتيجة: number
// استخراج معاملات الدالة
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function processUser(id: number, name: string, active: boolean) {
// ...
}
type ProcessUserParams = Parameters<typeof processUser>;
// النتيجة: [id: number, name: string, active: boolean]
// استخراج المعامل الأول
type FirstParameter<T> = T extends (first: infer F, ...args: any[]) => any ? F : never;
type FirstParam = FirstParameter<typeof processUser>;
// النتيجة: number
>
infer فقط داخل جملة extends للنوع الشرطي.
الأنواع الشرطية التوزيعية
عندما تعمل الأنواع الشرطية على نوع عام، تصبح توزيعية عند إعطائها نوع اتحاد. هذا يعني أن النوع الشرطي يُطبق على كل عضو في الاتحاد.
<// مثال توزيعي بسيط
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumberArray = ToArray<string | number>;
// النتيجة: string[] | number[] (وليس (string | number)[])
// تصفية null من الاتحاد
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 | 'specific';
type OnlyStrings = ExtractStrings<Mixed>;
// النتيجة: string | 'specific'
// منع التوزيع مع tuple
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type CombinedArray = ToArrayNonDist<string | number>;
// النتيجة: (string | number)[]
>
مثال عملي: مُرسل أحداث آمن من حيث النوع
<// تعريف خريطة الأحداث
interface EventMap {
'user:login': { userId: number; timestamp: Date };
'user:logout': { userId: number };
'data:update': { id: string; data: any };
'error': { message: string; code: number };
}
// استخراج أسماء الأحداث
type EventName = keyof EventMap;
// إنشاء مُرسل أحداث آمن من حيث النوع
class TypedEventEmitter<T extends Record<string, any>> {
private listeners: {
[K in keyof T]?: Array<(payload: T[K]) => void>;
} = {};
on<K extends keyof T>(event: K, listener: (payload: T[K]) => void): void {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event]!.push(listener);
}
emit<K extends keyof T>(event: K, payload: T[K]): void {
const eventListeners = this.listeners[event];
if (eventListeners) {
eventListeners.forEach(listener => listener(payload));
}
}
off<K extends keyof T>(event: K, listener: (payload: T[K]) => void): void {
const eventListeners = this.listeners[event];
if (eventListeners) {
this.listeners[event] = eventListeners.filter(l => l !== listener) as any;
}
}
}
// الاستخدام
const emitter = new TypedEventEmitter<EventMap>();
// آمن من حيث النوع: يتم استدلال الحمولة بشكل صحيح
emitter.on('user:login', (payload) => {
console.log(`المستخدم ${payload.userId} سجّل الدخول في ${payload.timestamp}`);
});
// آمن من حيث النوع: يجب توفير الحمولة الصحيحة
emitter.emit('user:login', {
userId: 123,
timestamp: new Date()
});
// خطأ: نوع حمولة خاطئ
// emitter.emit('user:login', { userId: '123' }); // خطأ: string غير قابل للتعيين لـ number
// خطأ: حدث غير معروف
// emitter.on('invalid:event', () => {}); // خطأ: invalid:event غير موجود في EventMap
>
- يأخذ واجهة بأنواع خصائص متنوعة
- ينشئ نوعًا جديدًا مع خصائص string فقط
- يحوّل كل اسم خاصية ليحتوي على بادئة 'validate'
- يغيّر كل نوع خاصية إلى دالة تحقق تُرجع boolean