أنواع الاتحاد والتقاطع
أنواع الاتحاد والتقاطع
يصبح نظام الأنواع في 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'
};
العمل مع أنواع الاتحاد
عند العمل مع أنواع الاتحاد، يسمح لك 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.
// كل نوع لديه مميز '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
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']
};
أمثلة عملية لنوع التقاطع
// الأنواع الأساسية
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; // خطأ
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"
- أنشئ اتحاداً مميزاً
Vehicleبثلاثة أنواع:Car(kind: 'car', doors: number, fuelType: string)،Motorcycle(kind: 'motorcycle', engineSize: number)، وBicycle(kind: 'bicycle', gearCount: number) - أنشئ دالة
describeVehicleالتي تأخذVehicleوترجع سلسلة وصف بناءً على المميز - أنشئ نوع تقاطع
ElectricVehicleالذي يجمعVehicleالأساسي مع{ batteryCapacity: number, range: number } - أنشئ دالة حارس النوع
isCarالتي تتحقق مما إذا كانت المركبة سيارة - اختبر أنواعك ودوالك مع بيانات نموذجية
الملخص
- أنواع الاتحاد تستخدم
|لإنشاء أنواع يمكن أن تكون واحدة من عدة أنواع (منطق أو) - تضييق النوع يحسن أنواع الاتحاد باستخدام typeof، instanceof، in، الصحة، وفحوصات المساواة
- الاتحادات المميزة تستخدم خاصية مشتركة (مميز) للتمييز بين الأنواع في الاتحاد
- أنواع التقاطع تستخدم
&لدمج عدة أنواع في واحد (منطق و) - أنواع الاتحاد مثالية لـ القيم التي يمكن أن تأخذ أشكالاً أو حالات متعددة
- أنواع التقاطع مثالية لـ تكوين الأنواع من مصادر أو خلائط متعددة
- حراس الأنواع يساعدون في تضييق أنواع الاتحاد بأمان والوصول إلى الخصائص الخاصة بالنوع
- الجمع بين الاتحادات والتقاطعات ينشئ تعريفات أنواع قوية ومرنة