لغة TypeScript

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

28 دقيقة الدرس 7 من 40

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

يصبح نظام الأنواع في TypeScript قوياً بشكل لا يصدق عندما تجمع الأنواع باستخدام أنواع الاتحاد (Union Types) وأنواع التقاطع (Intersection Types). تسمح لك عوامل الأنواع المتقدمة هذه بإنشاء تعريفات أنواع مرنة وتعبيرية تصمم بدقة هياكل البيانات في تطبيقك.

أنواع الاتحاد

نوع الاتحاد يصف قيمة يمكن أن تكون واحدة من عدة أنواع. تقوم بإنشاء أنواع الاتحاد باستخدام عامل الشريط العمودي (|). يمكن أن تكون القيمة أي من الأنواع في الاتحاد.

أنواع الاتحاد الأساسية:

// اتحاد من الأنواع البدائية
let id: string | number;

id = 'abc123'; // صحيح
id = 12345;     // صحيح
// id = true;   // خطأ: النوع 'boolean' غير قابل للتعيين

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

let orderStatus: Status;
orderStatus = 'pending';   // صحيح
orderStatus = 'approved';  // صحيح
// orderStatus = 'shipped'; // خطأ: ليس في الاتحاد

// اتحاد من أنواع الكائنات
type CreditCard = {
  type: 'credit';
  cardNumber: string;
  cvv: string;
};

type PayPal = {
  type: 'paypal';
  email: string;
};

type BankTransfer = {
  type: 'bank';
  accountNumber: string;
  routingNumber: string;
};

type PaymentMethod = CreditCard | PayPal | BankTransfer;

// استخدام نوع الاتحاد
const payment1: PaymentMethod = {
  type: 'credit',
  cardNumber: '1234-5678-9012-3456',
  cvv: '123'
};

const payment2: PaymentMethod = {
  type: 'paypal',
  email: 'user@example.com'
};
ملاحظة: غالباً ما يشار إلى أنواع الاتحاد باسم أنواع "أو" لأن القيمة يمكن أن تكون من النوع A أو النوع B أو النوع C. إنها مثالية لتمثيل القيم التي يمكن أن تأخذ أشكالاً متعددة.

العمل مع أنواع الاتحاد

عند العمل مع أنواع الاتحاد، يسمح لك TypeScript فقط بالوصول إلى الخصائص المشتركة بين جميع الأنواع في الاتحاد:

الوصول إلى خصائص نوع الاتحاد:

type Dog = {
  name: string;
  breed: string;
  bark(): void;
};

type Cat = {
  name: string;
  color: string;
  meow(): void;
};

type Pet = Dog | Cat;

function getPetName(pet: Pet): string {
  // ✓ صحيح: 'name' موجود في كل من Dog و Cat
  return pet.name;
}

function getPetInfo(pet: Pet): void {
  console.log(pet.name); // ✓ صحيح

  // ✗ خطأ: الخاصية 'breed' غير موجودة في النوع 'Pet'
  // console.log(pet.breed);

  // ✗ خطأ: الخاصية 'bark' غير موجودة في النوع 'Pet'
  // pet.bark();
}

تضييق النوع

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

تقنيات تضييق النوع:

// 1. تضييق typeof
function formatValue(value: string | number): string {
  if (typeof value === 'string') {
    // TypeScript يعرف أن value هي string هنا
    return value.toUpperCase();
  } else {
    // TypeScript يعرف أن value هي number هنا
    return value.toFixed(2);
  }
}

// 2. تضييق الصحة
function printLength(value: string | null | undefined): void {
  if (value) {
    // TypeScript يعرف أن value هي string هنا
    console.log(value.length);
  } else {
    console.log('No value provided');
  }
}

// 3. تضييق المساواة
function processInput(input: string | number, reference: string | number): void {
  if (input === reference) {
    // كلاهما من نفس النوع هنا (ولكن يمكن أن يكون string أو number)
    console.log(input.toString());
  }
}

// 4. تضييق عامل in
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird): void {
  if ('swim' in animal) {
    // TypeScript يعرف أن animal هي Fish
    animal.swim();
  } else {
    // TypeScript يعرف أن animal هي Bird
    animal.fly();
  }
}

// 5. تضييق instanceof
class Car {
  drive() {
    console.log('Driving...');
  }
}

class Boat {
  sail() {
    console.log('Sailing...');
  }
}

function operate(vehicle: Car | Boat): void {
  if (vehicle instanceof Car) {
    // TypeScript يعرف أن vehicle هي Car
    vehicle.drive();
  } else {
    // TypeScript يعرف أن vehicle هي Boat
    vehicle.sail();
  }
}
نصيحة: تحليل تدفق التحكم في TypeScript ذكي بما يكفي لتتبع تغييرات النوع من خلال المنطق الشرطي المعقد. استخدم تقنيات التضييق هذه للوصول إلى الخصائص الخاصة بالنوع بأمان.

اتحادات مميزة

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

نمط الاتحاد المميز:

// كل نوع لديه مميز 'kind'
type Circle = {
  kind: 'circle';
  radius: number;
};

type Rectangle = {
  kind: 'rectangle';
  width: number;
  height: number;
};

type Triangle = {
  kind: 'triangle';
  base: number;
  height: number;
};

type Shape = Circle | Rectangle | Triangle;

// TypeScript يضيق النوع بناءً على المميز
function calculateArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      // TypeScript يعرف أن shape هي Circle هنا
      return Math.PI * shape.radius ** 2;

    case 'rectangle':
      // TypeScript يعرف أن shape هي Rectangle هنا
      return shape.width * shape.height;

    case 'triangle':
      // TypeScript يعرف أن shape هي Triangle هنا
      return (shape.base * shape.height) / 2;

    default:
      // فحص الشمولية
      const _exhaustive: never = shape;
      return _exhaustive;
  }
}

// الاستخدام
const circle: Shape = { kind: 'circle', radius: 5 };
const rect: Shape = { kind: 'rectangle', width: 10, height: 20 };
const triangle: Shape = { kind: 'triangle', base: 8, height: 6 };

console.log(calculateArea(circle));   // 78.54
console.log(calculateArea(rect));     // 200
console.log(calculateArea(triangle)); // 24
ملاحظة: يجب أن تحتوي خاصية المميز (مثل kind أو type) على أنواع حرفية. هذا يسمح لـ TypeScript بتضييق نوع الاتحاد في جمل switch والشروط if.

مثال واقعي للاتحاد المميز

معالجة استجابة API:

// تعريف اتحاد مميز لاستجابات API
type SuccessResponse = {
  status: 'success';
  data: {
    id: string;
    name: string;
    email: string;
  };
  timestamp: Date;
};

type ErrorResponse = {
  status: 'error';
  error: {
    code: string;
    message: string;
  };
  timestamp: Date;
};

type LoadingResponse = {
  status: 'loading';
  progress: number;
};

type ApiResponse = SuccessResponse | ErrorResponse | LoadingResponse;

// معالجة الاستجابة بناءً على الحالة
function handleResponse(response: ApiResponse): void {
  switch (response.status) {
    case 'success':
      // TypeScript يعرف أن response هي SuccessResponse
      console.log('User:', response.data.name);
      console.log('Email:', response.data.email);
      break;

    case 'error':
      // TypeScript يعرف أن response هي ErrorResponse
      console.error('Error:', response.error.message);
      console.error('Code:', response.error.code);
      break;

    case 'loading':
      // TypeScript يعرف أن response هي LoadingResponse
      console.log('Loading:', response.progress + '%');
      break;
  }
}

// الاستخدام
const successResp: ApiResponse = {
  status: 'success',
  data: { id: '123', name: 'John', email: 'john@example.com' },
  timestamp: new Date()
};

const errorResp: ApiResponse = {
  status: 'error',
  error: { code: 'AUTH_FAILED', message: 'Invalid credentials' },
  timestamp: new Date()
};

handleResponse(successResp);
handleResponse(errorResp);

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

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

أنواع التقاطع الأساسية:

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

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

type HasAuthor = {
  author: string;
};

// نوع التقاطع يجمع الثلاثة
type BlogPost = HasId & HasTimestamps & HasAuthor & {
  title: string;
  content: string;
  tags: string[];
};

// الاستخدام - يجب أن تحتوي على جميع الخصائص
const post: BlogPost = {
  id: 'post-001',
  createdAt: new Date('2024-01-15'),
  updatedAt: new Date('2024-02-14'),
  author: 'Jane Doe',
  title: 'TypeScript Guide',
  content: 'Learn TypeScript...',
  tags: ['typescript', 'programming']
};
ملاحظة: غالباً ما يشار إلى أنواع التقاطع باسم أنواع "و" لأن القيمة يجب أن تستوفي النوع A والنوع B والنوع C. إنها مثالية لتكوين الأنواع من مصادر متعددة.

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

الخلائط والتركيب:

// الأنواع الأساسية
type Serializable = {
  serialize(): string;
};

type Validatable = {
  validate(): boolean;
  errors: string[];
};

type Saveable = {
  save(): Promise<void>;
};

// نوع مركب باستخدام التقاطعات
type FormField = Serializable & Validatable & Saveable & {
  name: string;
  value: unknown;
};

// التطبيق
class TextField implements FormField {
  name: string;
  value: string;
  errors: string[] = [];

  constructor(name: string, value: string) {
    this.name = name;
    this.value = value;
  }

  serialize(): string {
    return JSON.stringify({ name: this.name, value: this.value });
  }

  validate(): boolean {
    this.errors = [];
    if (!this.value || this.value.trim() === '') {
      this.errors.push('Field cannot be empty');
      return false;
    }
    return true;
  }

  async save(): Promise<void> {
    if (this.validate()) {
      console.log('Saving:', this.serialize());
      // محاكاة استدعاء API
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
}

// الاستخدام
const emailField = new TextField('email', 'user@example.com');
emailField.save();

الجمع بين الاتحادات والتقاطعات

يمكنك الجمع بين أنواع الاتحاد والتقاطع لإنشاء تعريفات أنواع متطورة:

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

// الأنواع الأساسية
type HasName = {
  name: string;
};

type HasAge = {
  age: number;
};

type HasEmail = {
  email: string;
};

// اتحاد من التقاطعات
type PersonalInfo = (HasName & HasAge) | (HasName & HasEmail);

// صحيح: لديه name و age
const person1: PersonalInfo = {
  name: 'John',
  age: 30
};

// صحيح: لديه name و email
const person2: PersonalInfo = {
  name: 'Jane',
  email: 'jane@example.com'
};

// صحيح: لديه الثلاثة
const person3: PersonalInfo = {
  name: 'Bob',
  age: 25,
  email: 'bob@example.com'
};

// غير صحيح: لديه name فقط
// const person4: PersonalInfo = {
//   name: 'Alice'
// };

// تقاطع من الاتحادات
type StringOrNumber = string | number;
type NumberOrBoolean = number | boolean;

// النتيجة هي number (النوع المشترك)
type OnlyNumber = StringOrNumber & NumberOrBoolean;

const value: OnlyNumber = 42; // يجب أن يكون number
// const value2: OnlyNumber = 'test'; // خطأ
// const value3: OnlyNumber = true; // خطأ
تحذير: عند تقاطع الأنواع مع خصائص متضاربة، ينشئ TypeScript نوع never. على سبيل المثال، { x: string } & { x: number } ينتج عنه never لأنه لا يمكن لأي قيمة أن تكون string ورقم في نفس الوقت.

أنماط الاتحاد المتقدمة

أعضاء الاتحاد الاختياريين:

// جعل أعضاء الاتحاد اختياريين
type Config = {
  theme: 'light' | 'dark';
  language: string;
  notifications?: {
    email: boolean;
    push: boolean;
  };
  advanced?: {
    debugMode: boolean;
    logLevel: 'info' | 'warn' | 'error';
  };
};

const basicConfig: Config = {
  theme: 'dark',
  language: 'en'
};

const advancedConfig: Config = {
  theme: 'light',
  language: 'en',
  notifications: {
    email: true,
    push: false
  },
  advanced: {
    debugMode: true,
    logLevel: 'info'
  }
};

// دالة تتعامل مع الاتحادات الاختيارية
function applyConfig(config: Config): void {
  console.log('Theme:', config.theme);

  if (config.notifications) {
    console.log('Email notifications:', config.notifications.email);
  }

  if (config.advanced) {
    console.log('Debug mode:', config.advanced.debugMode);
  }
}

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

أنشئ دوال حارس النوع المخصصة للتحقق من نوع الاتحاد المعقد:

حراس الأنواع المخصصة:

// تعريف أنواع الاتحاد
type Admin = {
  role: 'admin';
  permissions: string[];
  accessLevel: number;
};

type User = {
  role: 'user';
  subscriptionTier: 'free' | 'premium';
};

type Guest = {
  role: 'guest';
  sessionId: string;
};

type Account = Admin | User | Guest;

// دوال حارس النوع
function isAdmin(account: Account): account is Admin {
  return account.role === 'admin';
}

function isUser(account: Account): account is User {
  return account.role === 'user';
}

function isGuest(account: Account): account is Guest {
  return account.role === 'guest';
}

// الاستخدام مع حراس الأنواع
function getAccountInfo(account: Account): string {
  if (isAdmin(account)) {
    // TypeScript يعرف أن account هي Admin
    return `Admin with ${account.permissions.length} permissions`;
  }

  if (isUser(account)) {
    // TypeScript يعرف أن account هي User
    return `${account.subscriptionTier} user`;
  }

  if (isGuest(account)) {
    // TypeScript يعرف أن account هي Guest
    return `Guest session: ${account.sessionId}`;
  }

  // فحص الشمولية
  const _exhaustive: never = account;
  return _exhaustive;
}

// الاختبار
const admin: Account = {
  role: 'admin',
  permissions: ['read', 'write', 'delete'],
  accessLevel: 10
};

const user: Account = {
  role: 'user',
  subscriptionTier: 'premium'
};

console.log(getAccountInfo(admin)); // "Admin with 3 permissions"
console.log(getAccountInfo(user));  // "premium user"
تمرين:
  1. أنشئ اتحاداً مميزاً Vehicle بثلاثة أنواع: Car (kind: 'car', doors: number, fuelType: string)، Motorcycle (kind: 'motorcycle', engineSize: number)، وBicycle (kind: 'bicycle', gearCount: number)
  2. أنشئ دالة describeVehicle التي تأخذ Vehicle وترجع سلسلة وصف بناءً على المميز
  3. أنشئ نوع تقاطع ElectricVehicle الذي يجمع Vehicle الأساسي مع { batteryCapacity: number, range: number }
  4. أنشئ دالة حارس النوع isCar التي تتحقق مما إذا كانت المركبة سيارة
  5. اختبر أنواعك ودوالك مع بيانات نموذجية

الملخص

  • أنواع الاتحاد تستخدم | لإنشاء أنواع يمكن أن تكون واحدة من عدة أنواع (منطق أو)
  • تضييق النوع يحسن أنواع الاتحاد باستخدام typeof، instanceof، in، الصحة، وفحوصات المساواة
  • الاتحادات المميزة تستخدم خاصية مشتركة (مميز) للتمييز بين الأنواع في الاتحاد
  • أنواع التقاطع تستخدم & لدمج عدة أنواع في واحد (منطق و)
  • أنواع الاتحاد مثالية لـ القيم التي يمكن أن تأخذ أشكالاً أو حالات متعددة
  • أنواع التقاطع مثالية لـ تكوين الأنواع من مصادر أو خلائط متعددة
  • حراس الأنواع يساعدون في تضييق أنواع الاتحاد بأمان والوصول إلى الخصائص الخاصة بالنوع
  • الجمع بين الاتحادات والتقاطعات ينشئ تعريفات أنواع قوية ومرنة