لغة TypeScript

أنواع الأدوات المساعدة

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

أنواع الأدوات المساعدة في TypeScript

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

Partial<T>

ينشئ نوع الأداة المساعدة Partial<T> نوعًا مع جعل جميع خصائص T اختيارية. هذا مفيد بشكل خاص عندما تحتاج إلى العمل مع كائنات قد تحتوي على مجموعة فرعية فقط من الخصائص المحددة.

كود TypeScript:
<interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// جميع الخصائص تصبح اختيارية
type PartialUser = Partial<User>;

const updateUser = (id: number, updates: Partial<User>) => {
  // يمكن تحديث خصائص محددة فقط
  console.log(`Updating user ${id}`, updates);
};

// استدعاءات صحيحة
updateUser(1, { name: 'John' });
updateUser(2, { email: 'jane@example.com', age: 30 });
updateUser(3, {}); // أيضًا صحيح
>
نصيحة: Partial<T> مثالي لدوال التحديث حيث لا تحتاج إلى توفير جميع خصائص الكائن.

Required<T>

عكس Partial، Required<T> يجعل جميع الخصائص في T إلزامية. هذا مفيد عندما يكون لديك واجهة بخصائص اختيارية ولكنك تحتاج إلى إصدار حيث تكون جميع الخصائص إلزامية.

كود TypeScript:
<interface UserProfile {
  username: string;
  bio?: string;
  avatar?: string;
  website?: string;
}

type CompleteProfile = Required<UserProfile>;

// خطأ: خصائص إلزامية مفقودة
const profile1: CompleteProfile = {
  username: 'johndoe'
  // خطأ: bio و avatar و website إلزامية
};

// صحيح
const profile2: CompleteProfile = {
  username: 'johndoe',
  bio: 'Developer',
  avatar: 'avatar.jpg',
  website: 'https://example.com'
};
>

Readonly<T>

ينشئ نوع الأداة المساعدة Readonly<T> نوعًا مع جعل جميع خصائص T للقراءة فقط، مما يعني أن الخصائص لا يمكن إعادة تعيينها بعد التعيين الأولي.

كود TypeScript:
<interface Config {
  apiUrl: string;
  timeout: number;
  retries: number;
}

const config: Readonly<Config> = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3
};

// خطأ: لا يمكن التعيين إلى 'apiUrl' لأنها خاصية للقراءة فقط
config.apiUrl = 'https://new-api.example.com';

// للقراءة فقط المتداخلة، استخدم نهجًا تكراريًا
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? DeepReadonly<T[P]>
    : T[P];
};
>
تحذير: Readonly<T> يجعل المستوى الأول فقط للقراءة فقط. للكائنات المتداخلة بعمق، تحتاج إلى نوع تكراري مخصص.

Record<K, T>

ينشئ نوع الأداة المساعدة Record<K, T> نوع كائن تكون مفاتيح خصائصه K وقيم خصائصه T. إنه مفيد لإنشاء أنواع ممثلة بمفاتيح محددة.

كود TypeScript:
<type UserRole = 'admin' | 'editor' | 'viewer';

interface Permission {
  read: boolean;
  write: boolean;
  delete: boolean;
}

// إنشاء كائن بالأدوار كمفاتيح والأذونات كقيم
const rolePermissions: Record<UserRole, Permission> = {
  admin: { read: true, write: true, delete: true },
  editor: { read: true, write: true, delete: false },
  viewer: { read: true, write: false, delete: false }
};

// مثال آخر: إنشاء قاموس
type PageTitle = Record<string, string>;

const pageTitles: PageTitle = {
  home: 'الصفحة الرئيسية',
  about: 'من نحن',
  contact: 'اتصل بنا'
};
>

Pick<T, K>

ينشئ نوع الأداة المساعدة Pick<T, K> نوعًا عن طريق اختيار مجموعة الخصائص K من T. يتيح لك هذا إنشاء نوع فرعي مع خصائص محددة فقط.

كود TypeScript:
<interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  stock: number;
  category: string;
  createdAt: Date;
}

// اختر الخصائص المطلوبة للعرض فقط
type ProductPreview = Pick<Product, 'id' | 'name' | 'price'>;

const preview: ProductPreview = {
  id: 1,
  name: 'لابتوب',
  price: 999.99
  // الخصائص الأخرى غير مسموح بها
};

// مفيد لاستجابات API
type ProductListItem = Pick<Product, 'id' | 'name' | 'price' | 'stock'>;
>

Omit<T, K>

ينشئ نوع الأداة المساعدة Omit<T, K> نوعًا عن طريق اختيار جميع الخصائص من T ثم إزالة K. إنه عكس Pick.

كود TypeScript:
<interface User {
  id: number;
  username: string;
  password: string;
  email: string;
  createdAt: Date;
}

// حذف البيانات الحساسة لاستجابات API
type PublicUser = Omit<User, 'password'>;

const publicUser: PublicUser = {
  id: 1,
  username: 'johndoe',
  email: 'john@example.com',
  createdAt: new Date()
  // كلمة المرور غير مضمنة
};

// حذف خصائص متعددة
type UserCreateDTO = Omit<User, 'id' | 'createdAt'>;

const newUser: UserCreateDTO = {
  username: 'janedoe',
  password: 'securepass123',
  email: 'jane@example.com'
};
>
ملاحظة: Omit<T, K> مُطبق كـ Pick<T, Exclude<keyof T, K>>، يجمع بين أنواع أدوات مساعدة متعددة.

Exclude<T, U>

ينشئ نوع الأداة المساعدة Exclude<T, U> نوعًا عن طريق استبعاد جميع أعضاء الاتحاد من T القابلة للتعيين إلى U.

كود TypeScript:
<type AllStatus = 'pending' | 'approved' | 'rejected' | 'cancelled';

// استبعاد حالة الإلغاء
type ActiveStatus = Exclude<AllStatus, 'cancelled'>;
// النتيجة: 'pending' | 'approved' | 'rejected'

// استبعاد قيم متعددة
type PositiveStatus = Exclude<AllStatus, 'rejected' | 'cancelled'>;
// النتيجة: 'pending' | 'approved'

// يعمل مع الأنواع أيضًا
type Primitive = string | number | boolean | null | undefined;
type NonNullishPrimitive = Exclude<Primitive, null | undefined>;
// النتيجة: string | number | boolean
>

Extract<T, U>

ينشئ نوع الأداة المساعدة Extract<T, U> نوعًا عن طريق استخراج جميع أعضاء الاتحاد من T القابلة للتعيين إلى U. إنه عكس Exclude.

كود TypeScript:
<type MixedType = string | number | boolean | null;

// استخرج string و number فقط
type StringOrNumber = Extract<MixedType, string | number>;
// النتيجة: string | number

// استخراج قيم محددة
type Status = 'success' | 'error' | 'pending' | 'idle';
type CompletedStatus = Extract<Status, 'success' | 'error'>;
// النتيجة: 'success' | 'error'

// مثال عملي
type EventType = 'click' | 'scroll' | 'keydown' | 'keyup';
type KeyboardEvent = Extract<EventType, `key${string}`>;
// النتيجة: 'keydown' | 'keyup'
>

NonNullable<T>

ينشئ نوع الأداة المساعدة NonNullable<T> نوعًا عن طريق استبعاد null و undefined من T.

كود TypeScript:
<type MaybeString = string | null | undefined;

// إزالة null و undefined
type DefiniteString = NonNullable<MaybeString>;
// النتيجة: string

// مثال عملي
interface User {
  id: number;
  name: string;
  email: string | null;
}

const getEmail = (user: User): NonNullable<User['email']> => {
  if (user.email === null) {
    throw new Error('البريد الإلكتروني مطلوب');
  }
  return user.email; // TypeScript يعرف أن هذا string
};

// مع إرجاع الدالة
type FunctionReturn = string | number | null | undefined;
type ValidReturn = NonNullable<FunctionReturn>;
// النتيجة: string | number
>

ReturnType<T>

ينشئ نوع الأداة المساعدة ReturnType<T> نوعًا يتكون من نوع الإرجاع للدالة T. هذا مفيد بشكل خاص عند العمل مع الدوال التي تريد الإشارة إلى أنواع إرجاعها.

كود TypeScript:
<function createUser(name: string, email: string) {
  return {
    id: Math.random(),
    name,
    email,
    createdAt: new Date()
  };
}

// استخراج نوع الإرجاع
type User = ReturnType<typeof createUser>;
// النتيجة: { id: number; name: string; email: string; createdAt: Date; }

// مع الدوال غير المتزامنة
async function fetchData() {
  return {
    data: [1, 2, 3],
    status: 'success' as const
  };
}

type FetchResult = ReturnType<typeof fetchData>;
// النتيجة: Promise<{ data: number[]; status: 'success'; }>

// فك Promise مع Awaited
type UnwrappedResult = Awaited<ReturnType<typeof fetchData>>;
// النتيجة: { data: number[]; status: 'success'; }
>

دمج أنواع الأدوات المساعدة

تأتي القوة الحقيقية لأنواع الأدوات المساعدة من دمجها لإنشاء تحويلات أنواع متطورة.

كود TypeScript:
<interface Article {
  id: number;
  title: string;
  content: string;
  author: string;
  publishedAt: Date;
  updatedAt: Date;
  tags: string[];
}

// إنشاء نوع لتحديثات المقالات (حقول اختيارية، بدون id)
type ArticleUpdate = Partial<Omit<Article, 'id' | 'publishedAt'>>;

// إنشاء نسخة للقراءة فقط من حقول محددة
type ArticleDisplay = Readonly<Pick<Article, 'title' | 'content' | 'author'>>;

// إنشاء نوع لإنشاء المقالات (بدون id أو طوابع زمنية)
type ArticleCreate = Omit<Article, 'id' | 'publishedAt' | 'updatedAt'>;

// إنشاء نسخة مطلوبة من نوع جزئي
type RequiredUpdate = Required<Partial<Article>>;
// نفس Article، لكن يوضح المفهوم
>
تمرين: أنشئ نوع أداة مساعدة يقوم بما يلي:
  1. يأخذ واجهة بخصائص id و name و email و password و createdAt
  2. يحذف حقل password
  3. يجعل جميع الحقول المتبقية للقراءة فقط
  4. يستخدم هذا النوع لدالة تُرجع كائن مستخدم آمن

أنواع الأدوات المساعدة المخصصة

يمكنك إنشاء أنواع الأدوات المساعدة الخاصة بك من خلال دمج الأنواع الممثلة والأنواع الشرطية في TypeScript.

كود TypeScript:
<// جعل خصائص محددة اختيارية
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type ProductWithOptionalDescription = PartialBy<Product, 'description'>;

// جعل خصائص محددة إلزامية
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

// أداة Nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };

// جزئي عميق
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
>
أفضل ممارسة: أنشئ أنواع أدوات مساعدة قابلة لإعادة الاستخدام في ملف مخصص types/utilities.ts واستوردها عبر مشروعك.

مثال عملي: خدمة API

مثال كامل:
<// تعريف الكيان الأساسي
interface BaseEntity {
  id: number;
  createdAt: Date;
  updatedAt: Date;
}

interface User extends BaseEntity {
  username: string;
  email: string;
  password: string;
  isActive: boolean;
}

// أنواع لعمليات مختلفة
type UserCreate = Omit<User, keyof BaseEntity>;
type UserUpdate = Partial<Omit<User, keyof BaseEntity | 'password'>>;
type UserPublic = Omit<User, 'password'>;
type UserResponse = Readonly<UserPublic>;

class UserService {
  create(data: UserCreate): Promise<UserResponse> {
    // التنفيذ
    return Promise.resolve({} as UserResponse);
  }

  update(id: number, data: UserUpdate): Promise<UserResponse> {
    // التنفيذ
    return Promise.resolve({} as UserResponse);
  }

  findById(id: number): Promise<UserResponse | null> {
    // التنفيذ
    return Promise.resolve(null);
  }
}

// الاستخدام
const service = new UserService();

service.create({
  username: 'johndoe',
  email: 'john@example.com',
  password: 'secure123',
  isActive: true
});

service.update(1, {
  email: 'newemail@example.com'
  // لا يمكن تحديث كلمة المرور هنا
});
>
ملخص: أنواع الأدوات المساعدة هي أدوات أساسية في TypeScript تساعدك على كتابة كود أكثر قابلية للصيانة وآمن من حيث النوع. أتقن هذه الأدوات المساعدة المدمجة وتعلم كيفية دمجها لتحويلات أنواع معقدة.