لنقل عندنا Type اسمه ToDo:
type ToDo = {
id: number;
title: string;
createdAt: Date;
isCompleted: boolean;
};
إذا عندنا Function بتعمل Update لـ ToDo، مش دائماً رح نبعث كل الـ Properties - ممكن بس نبعث title مثلاً:
function updateToDo(item: ToDo): void { /* ... */ }
updateToDo({ title: "مهمة محدّثة" }); // Error!
Argument of type '{ title: string; }' is not assignable to parameter of type 'ToDo'
الحل الساذج هو إنشاء Type ثاني بكل الـ Properties اختيارية:
type UpdatedToDo
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
لكن هذا بيخلق مشكلة - صرنا نحافظ على Typeين للشيء نفسه. لو غيّرت title لـ name بالـ ToDo الأساسي، لازم تتذكر تغيّره بالثاني كمان.
Partial<T> هو Mapped Type جاهز بـ TypeScript، يحول كل الـ Properties لـ Optional:
type UpdatedToDo = Partial<ToDo>;إذا عملت hover على UpdatedToDo:
type UpdatedToDo = {
id?: number;
title?: string;
createdAt?: Date;
isCompleted?: boolean;
}الميزة الكبيرة: UpdatedToDo مرتبط بـ ToDo. لو غيّرت title لـ name بالـ Type الأساسي، TypeScript رح تخبرك تلقائياً وين الـ UpdatedToDo محتاج يتغير.
Required<T> عكس Partial - يحول كل الـ Properties لـ مطلوبة، حتى اللي كانت Optional:
type Order = {
product: string;
shippingAddress?: string; // optional
};
type RequiredOrder = Required<Order>;
// shippingAddress أصبح مطلوباً الآنReadonly<T> يمنع أي تعديل على الـ Properties بعد الإنشاء:
type ToDoReadOnly = Readonly<ToDo>;
const item: ToDoReadOnly = {
id: 1,
title: "مهمة",
createdAt: new Date(),
isCompleted: false,
};
item.id = 2; // Error: Cannot assign to 'id' because it is a read-only propertyمفيد عندما تريد تلزم الـ Developers إنهم ما يعدّلوا على Object بعد إنشائه.
Omit<T, K> يأخذ الـ Type ويحذف منه Properties معينة:
type CreateToDo = Omit<ToDo, "id">;
// CreateToDo = { title: string; createdAt: Date; isCompleted: boolean }مفيد عندما تبعث بيانات للـ Backend ولا تريد أن يحدد الـ Client الـ id - الـ Backend هو اللي يعمله.
ممكن تحذف أكثر من Property باستخدام |:
type CreateToDo = Omit<ToDo, "id" | "createdAt">;Pick<T, K> عكس Omit - تأخذ فقط الـ Properties اللي تحددها:
type ToDoPreview = Pick<ToDo, "id" | "title">;
// ToDoPreview = { id: number; title: string }| Type | الوظيفة |
|---|---|
Partial<T> | يجعل كل الـ Properties اختيارية |
Required<T> | يجعل كل الـ Properties مطلوبة |
Readonly<T> | يمنع التعديل على الـ Properties |
Omit<T, K> | يحذف Properties معينة |
Pick<T, K> | يأخذ Properties معينة فقط |
للمزيد، ابحث عن "Mapped Types TypeScript" بالموقع الرسمي لـ TypeScript - ستجد documentation كامل بكل الـ Mapped Types.
لنقل عندنا Type لـ API Response، والـ Response ممكن يكون بيانات Users أو Products:
type User = {
id: number;
name: string;
email: string;
};
type Product = {
id: number;
name: string;
price: number;
};
type UserAPIResponse = {
status: number;
message: string;
data: User[];
};
type ProductAPIResponse = {
status: number;
message: string;
data: Product[];
};نفس البنية بالضبط، بس data مختلفة. عم نكرر نفس الكود.
Generic هو متغير للـ Types - يعني بتحدد شو هو الـ Type وقت الاستخدام، مش وقت التعريف.
type APIResponse<T> = {
status: number;
message: string;
data: T;
};T هنا هو الـ Generic, ممكن تسميه أي شيء (T, K, U, V)، لكن T اختصار Type هو الأكثر شيوعاً.
الآن ممكن تستخدمه مع أي Type:
type UserAPIResponse = APIResponse<User[]>;
type ProductAPIResponse = APIResponse<Product[]>;إذا عملت hover على UserAPIResponse:
type UserAPIResponse = {
status: number;
message: string;
data: User[];
}وإذا عملت hover على ProductAPIResponse:
type ProductAPIResponse = {
status: number;
message: string;
data: Product[];
}Generics بتساعدنا نقلل تكرار الكود وفي نفس الوقت نحافظ على الـ Type Safety الكاملة.
لنقل عندنا Interface-ين:
interface Chicken {
eat: (food: string) => void;
}
interface Bear {
eat: (food: string) => void;
roar: () => void;
}لدينا Function تأخذ يا Chicken يا Bear:
function describeAnimal(animal: Chicken | Bear): void {
// كيف نعرف شو هو النوع؟
}الـ eat موجود بالاثنين، لكن roar موجودة بـ Bear فقط.
طريقة للوصول لـ Property معينة هي Type Casting باستخدام as:
function describeAnimal(animal: Chicken | Bear): void {
if ((animal as Bear).roar !== undefined) {
(animal as Bear).roar();
}
}هذا يشتغل، لكنه متعب - لازم تكرر as Bear في كل مكان.
isالأفضل هو إنشاء Function مخصصة للـ Type Guard:
function isBear(animal: Chicken | Bear): animal is Bear {
return (animal as Bear).roar !== undefined;
}الـ animal is Bear هو الـ Return Type - يعني هذه الدالة بترجع true إذا كان animal من نوع Bear.
الاستخدام:
function describeAnimal(animal: Chicken | Bear): void {
if (isBear(animal)) {
animal.roar(); // TypeScript يعرف إنه Bear هنا تلقائياً
} else {
animal.eat("حبوب"); // TypeScript يعرف إنه Chicken هنا تلقائياً
}
}داخل الـ if، TypeScript بتعمل Type Narrowing تلقائياً - بتعرف إنه Bear. وبالـ else بتعرف إنه Chicken.
مقارنة بين الطريقتين:
| Type Casting | Type Guard | |
|---|---|---|
| الكود | (animal as Bear).roar() | animal.roar() |
| الأمان | محدود | كامل |
| إعادة الاستخدام | ✗ | ✓ |
هذه نهاية السلسلة الأساسية. رح نزل فيديوهات بالمستقبل لنغطي مواضيع أعمق بـ TypeScript.