لغة TypeScript

الأسماء المستعارة للأنواع والواجهات

25 دقيقة الدرس 6 من 40

الأسماء المستعارة للأنواع والواجهات

توفر TypeScript طريقتين رئيسيتين لتعريف الأنواع المخصصة: الأسماء المستعارة للأنواع (Type Aliases) والواجهات (Interfaces). كلاهما يسمح لك بإنشاء تعريفات أنواع قابلة لإعادة الاستخدام، لكن لديهما اختلافات دقيقة في القدرات وحالات الاستخدام. فهم متى تستخدم كل منهما أمر بالغ الأهمية لكتابة كود TypeScript نظيف وقابل للصيانة.

الأسماء المستعارة للأنواع

الاسم المستعار للنوع ينشئ اسماً جديداً لأي نوع. تقوم بتعريفه باستخدام الكلمة المفتاحية type متبوعة باسم الاسم المستعار وتعريف النوع.

اسم مستعار أساسي للنوع:

// اسم مستعار بسيط للنوع البدائي
type UserID = string | number;

// اسم مستعار لنوع لشكل الكائن
type User = {
  id: UserID;
  name: string;
  email: string;
  age?: number;
};

// استخدام الاسم المستعار للنوع
const user1: User = {
  id: 123,
  name: 'John Doe',
  email: 'john@example.com'
};

const user2: User = {
  id: 'abc-456',
  name: 'Jane Smith',
  email: 'jane@example.com',
  age: 28
};
ملاحظة: الأسماء المستعارة للأنواع يمكن أن تمثل أي نوع، بما في ذلك البدائيات، الاتحادات، المجموعات، وأشكال الكائنات المعقدة. إنها مفيدة بشكل خاص لأنواع الاتحاد وتركيبات الأنواع المعقدة.

الأسماء المستعارة للأنواع المعقدة

تتفوق الأسماء المستعارة للأنواع في تمثيل تركيبات الأنواع المعقدة:

أسماء مستعارة للأنواع المعقدة:

// اسم مستعار لنوع الاتحاد
type Status = 'pending' | 'approved' | 'rejected';

// اسم مستعار لنوع المجموعة
type Coordinate = [number, number];

// اسم مستعار لنوع الدالة
type MathOperation = (a: number, b: number) => number;

// كائن معقد مع أنواع متداخلة
type Product = {
  id: string;
  name: string;
  price: number;
  category: 'electronics' | 'clothing' | 'food';
  tags: string[];
  metadata: {
    createdAt: Date;
    updatedAt: Date;
    views: number;
  };
};

// استخدام اسم مستعار لنوع الدالة
const add: MathOperation = (a, b) => a + b;
const multiply: MathOperation = (a, b) => a * b;

// استخدام النوع المعقد
const laptop: Product = {
  id: 'prod-001',
  name: 'MacBook Pro',
  price: 2499,
  category: 'electronics',
  tags: ['computer', 'apple', 'laptop'],
  metadata: {
    createdAt: new Date(),
    updatedAt: new Date(),
    views: 1250
  }
};

الواجهات

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

واجهة أساسية:

// تعريف الواجهة
interface Person {
  firstName: string;
  lastName: string;
  age: number;
  email?: string; // خاصية اختيارية
  readonly id: string; // لا يمكن تعديلها بعد الإنشاء
}

// استخدام الواجهة
const person: Person = {
  id: 'person-123',
  firstName: 'Alice',
  lastName: 'Johnson',
  age: 30
};

// person.id = 'new-id'; // خطأ: لا يمكن التعيين إلى 'id' لأنها خاصية للقراءة فقط

// دالة في الواجهة
interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
}

const calc: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};
نصيحة: استخدم readonly للخصائص التي يجب تعيينها فقط أثناء التهيئة. هذا يساعد على منع التعديلات العرضية ويجعل الكود الخاص بك أكثر قابلية للتنبؤ.

توسيع الواجهات

واحدة من أقوى ميزات الواجهات هي القدرة على توسيع واجهات أخرى باستخدام الكلمة المفتاحية extends. هذا يسمح لك ببناء هرميات أنواع معقدة.

توسيع الواجهة:

// الواجهة الأساسية
interface Animal {
  name: string;
  age: number;
  makeSound(): void;
}

// الواجهة الموسعة
interface Dog extends Animal {
  breed: string;
  wagTail(): void;
}

// واجهة تمتد من واجهات متعددة
interface Bird {
  wingspan: number;
  canFly: boolean;
}

interface FlyingAnimal extends Animal, Bird {
  altitude: number;
}

// التطبيق
const myDog: Dog = {
  name: 'Buddy',
  age: 5,
  breed: 'Golden Retriever',
  makeSound: () => console.log('Woof!'),
  wagTail: () => console.log('*tail wagging*')
};

const eagle: FlyingAnimal = {
  name: 'Eagle',
  age: 3,
  wingspan: 2.5,
  canFly: true,
  altitude: 1000,
  makeSound: () => console.log('Screech!')
};

أنواع التقاطع

أنواع التقاطع تجمع عدة أنواع في واحد باستخدام عامل &. النوع الناتج له جميع الخصائص من جميع الأنواع المدمجة.

أنواع التقاطع مع الأسماء المستعارة للأنواع:

// الأنواع الفردية
type Identifiable = {
  id: string;
};

type Timestamped = {
  createdAt: Date;
  updatedAt: Date;
};

type Versioned = {
  version: number;
};

// نوع التقاطع يجمع الثلاثة
type Entity = Identifiable & Timestamped & Versioned;

// الاستخدام
const article: Entity = {
  id: 'article-001',
  createdAt: new Date('2024-01-01'),
  updatedAt: new Date('2024-02-14'),
  version: 3
};

// التقاطع مع حروف الكائن
type Name = {
  firstName: string;
  lastName: string;
};

type Contact = {
  email: string;
  phone: string;
};

type Employee = Name & Contact & {
  employeeId: string;
  department: string;
};

const employee: Employee = {
  firstName: 'Sarah',
  lastName: 'Williams',
  email: 'sarah@company.com',
  phone: '+1234567890',
  employeeId: 'EMP-789',
  department: 'Engineering'
};
ملاحظة: أنواع التقاطع تنشئ نوعاً يجب أن يستوفي جميع الأنواع المدمجة. إذا كانت هناك خصائص متضاربة، فقد ينتج عن التقاطع نوع never.

الأسماء المستعارة للأنواع مقابل الواجهات: الاختلافات الرئيسية

بينما يمكن لكليهما تعريف أشكال الكائنات، هناك اختلافات مهمة بين الأسماء المستعارة للأنواع والواجهات:

1. دمج الإعلان (الواجهات فقط):

// يمكن دمج الواجهات
interface Window {
  title: string;
}

interface Window {
  size: number;
}

// يتم دمج كلا الإعلانين في واحد
const window: Window = {
  title: 'My Window',
  size: 1024
};

// لا يمكن دمج الأسماء المستعارة للأنواع
type Config = {
  theme: string;
};

// خطأ: معرف مكرر 'Config'
// type Config = {
//   language: string;
// };
2. التوسيع مقابل التقاطع:

// توسيع الواجهة (باستخدام extends)
interface BaseUser {
  id: string;
  name: string;
}

interface AdminUser extends BaseUser {
  permissions: string[];
}

// تقاطع الاسم المستعار للنوع (باستخدام &)
type BaseUserType = {
  id: string;
  name: string;
};

type AdminUserType = BaseUserType & {
  permissions: string[];
};

// كلاهما يحققان نتائج مماثلة
const admin1: AdminUser = {
  id: 'admin-1',
  name: 'Admin User',
  permissions: ['read', 'write', 'delete']
};

const admin2: AdminUserType = {
  id: 'admin-2',
  name: 'Admin User',
  permissions: ['read', 'write', 'delete']
};
3. الأسماء المستعارة للأنواع يمكن أن تمثل أي شيء:

// الأسماء المستعارة للأنواع تعمل مع البدائيات، الاتحادات، المجموعات
type StringOrNumber = string | number;
type Coordinates = [number, number];
type Callback = () => void;

// الواجهات محدودة بأشكال الكائنات
// interface StringOrNumber = string | number; // خطأ!
// interface Coordinates = [number, number]; // خطأ!

// ومع ذلك، يمكن للواجهات تمثيل الدوال
interface CallbackInterface {
  (): void;
}

// كلاهما يعمل لأنواع الدالة
const callback1: Callback = () => console.log('Called');
const callback2: CallbackInterface = () => console.log('Called');
تحذير: كن حذراً مع دمج الإعلان. بينما هو مفيد لتوسيع أنواع مكتبات الطرف الثالث، فإن الدمج العرضي يمكن أن يؤدي إلى سلوك غير متوقع. الأسماء المستعارة للأنواع تمنع هذا من خلال عدم السماح بالتكرارات.

متى تستخدم الأسماء المستعارة للأنواع مقابل الواجهات

إليك إرشادات عملية للاختيار بين الأسماء المستعارة للأنواع والواجهات:

استخدم الواجهات عندما:

// 1. تعريف أشكال الكائنات (خاصة واجهات برمجة التطبيقات العامة)
interface ApiResponse {
  data: unknown;
  status: number;
  message: string;
}

// 2. قد تحتاج إلى دمج الإعلان
interface CustomWindow {
  myCustomProperty: string;
}

// 3. العمل مع الفئات (الواجهات تعمل بشكل طبيعي مع implements)
interface Drawable {
  draw(): void;
}

class Circle implements Drawable {
  draw() {
    console.log('Drawing circle');
  }
}

// 4. بناء هرميات أنواع قابلة للتوسيع
interface Vehicle {
  brand: string;
  model: string;
}

interface Car extends Vehicle {
  doors: number;
}

interface ElectricCar extends Car {
  batteryCapacity: number;
}
استخدم الأسماء المستعارة للأنواع عندما:

// 1. تعريف أنواع الاتحاد
type Result = 'success' | 'error' | 'pending';

// 2. تعريف أنواع المجموعات
type RGB = [number, number, number];

// 3. معالجات الأنواع المعقدة
type Nullable<T> = T | null;
type ReadonlyProps<T> = {
  readonly [K in keyof T]: T[K];
};

// 4. أنواع الدالة
type EventHandler = (event: Event) => void;

// 5. الأنواع المعينة وأنواع الأدوات المساعدة
type Optional<T> = {
  [K in keyof T]?: T[K];
};

// 6. أنواع التقاطع
type UserWithTimestamps = User & Timestamped & Identifiable;
نصيحة: يفضل العديد من المطورين الواجهات لأشكال الكائنات والأسماء المستعارة للأنواع لكل شيء آخر. هذه استراتيجية افتراضية جيدة، ولكن استخدم ما يعمل بشكل أفضل لاحتياجات مشروعك.

الجمع بين الأسماء المستعارة للأنواع والواجهات

يمكنك المزج والمطابقة بين الأسماء المستعارة للأنواع والواجهات في نفس قاعدة الكود:

النهج الهجين:

// اسم مستعار للنوع للاتحاد
type UserRole = 'admin' | 'editor' | 'viewer';

// واجهة تستخدم اسم مستعار للنوع
interface UserAccount {
  id: string;
  username: string;
  role: UserRole; // استخدام الاسم المستعار للنوع
  metadata: UserMetadata;
}

// اسم مستعار للنوع يستخدم الواجهة
type UserMetadata = {
  lastLogin: Date;
  loginCount: number;
};

// واجهة تمتد مع التقاطع
interface PremiumUser extends UserAccount {
  subscriptionLevel: 'basic' | 'pro' | 'enterprise';
}

// اسم مستعار للنوع يجمع الواجهة والنوع
type FullUserProfile = UserAccount & {
  preferences: {
    theme: 'light' | 'dark';
    language: string;
  };
  notifications: boolean;
};

// التطبيق
const premiumUser: PremiumUser = {
  id: 'user-001',
  username: 'john_doe',
  role: 'admin',
  subscriptionLevel: 'pro',
  metadata: {
    lastLogin: new Date(),
    loginCount: 150
  }
};

const fullProfile: FullUserProfile = {
  id: 'user-002',
  username: 'jane_smith',
  role: 'editor',
  metadata: {
    lastLogin: new Date(),
    loginCount: 75
  },
  preferences: {
    theme: 'dark',
    language: 'en'
  },
  notifications: true
};
تمرين:
  1. أنشئ واجهة Book مع الخصائص: title، author، isbn (للقراءة فقط)، وpublishedYear اختيارية
  2. أنشئ اسم مستعار للنوع BookFormat كاتحاد من 'hardcover'، 'paperback'، 'ebook'، و'audiobook'
  3. أنشئ واجهة PhysicalBook التي تمتد من Book وتضيف format: BookFormat وpageCount: number
  4. أنشئ اسم مستعار للنوع DigitalBook باستخدام التقاطع الذي يجمع Book مع كائن يحتوي على fileSize: number وformat: 'ebook' | 'audiobook'
  5. أنشئ نسخاً من كل من PhysicalBook وDigitalBook مع بيانات نموذجية

الملخص

  • الأسماء المستعارة للأنواع تستخدم الكلمة المفتاحية type ويمكن أن تمثل أي نوع (البدائيات، الاتحادات، المجموعات، الكائنات، الدوال)
  • الواجهات تستخدم الكلمة المفتاحية interface وهي متخصصة لأشكال الكائنات
  • الواجهات تدعم التوسيع مع الكلمة المفتاحية extends ودمج الإعلان
  • الأسماء المستعارة للأنواع تدعم التقاطعات مع عامل & لدمج الأنواع
  • اختر الواجهات لأشكال الكائنات، وواجهات برمجة التطبيقات العامة، والهرميات القابلة للتوسيع
  • اختر الأسماء المستعارة للأنواع للاتحادات، المجموعات، معالجات الأنواع المعقدة، وأنواع الدوال
  • يمكن دمج كلاهما في نفس قاعدة الكود لأقصى قدر من المرونة