أنماط أنواع الأدوات المساعدة المتقدمة
أنماط أنواع الأدوات المساعدة المتقدمة
أنواع الأدوات المساعدة في TypeScript هي كتل بناء قوية، لكن السحر الحقيقي يحدث عندما تجمعها وتوسعها لإنشاء تحويلات نوع متطورة مخصصة. في هذا الدرس، سنستكشف أنماط أنواع الأدوات المساعدة المتقدمة بما في ذلك DeepPartial و DeepReadonly و PathKeys للوصول إلى الكائنات المتداخلة، وتقنيات بناء أدوات نوع قابلة لإعادة الاستخدام تحل تحديات الكتابة المعقدة.
لماذا نبني أنواع أدوات مساعدة مخصصة؟
أنواع الأدوات المساعدة المخصصة تمكنك من:
- تقليل التكرار: عرّف تحويلات النوع المعقدة مرة واحدة وأعد استخدامها في جميع أنحاء قاعدة الكود الخاصة بك
- فرض الاتساق: وحّد كيفية تحويل الأنواع عبر تطبيقك
- تحسين قابلية الصيانة: مركز منطق النوع في أدوات مساعدة قابلة لإعادة الاستخدام بدلاً من تكرارها
- التعبير عن النية: أنشئ أسماء أنواع موثقة ذاتيًا تنقل الغرض بوضوح
- معالجة الحالات الخاصة: ابنِ أدوات مساعدة تعالج بشكل صحيح البنيات المتداخلة المعقدة
DeepPartial - خصائص اختيارية متكررة
النوع المدمج Partial<T> يجعل فقط الخصائص على المستوى الأعلى اختيارية. DeepPartial يجعل بشكل متكرر جميع الخصائص المتداخلة اختيارية:
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: DeepPartial<T[P]>
: T[P];
};
// مثال الاستخدام
interface User {
id: number;
profile: {
firstName: string;
lastName: string;
address: {
street: string;
city: string;
country: string;
};
};
preferences: {
theme: 'light' | 'dark';
notifications: {
email: boolean;
push: boolean;
};
};
}
// جميع الخصائص على جميع المستويات تصبح اختيارية
type PartialUser = DeepPartial<User>;
const updateUser: PartialUser = {
profile: {
address: {
city: 'نيويورك', // يمكن تحديث خاصية متداخلة واحدة فقط
},
},
};
DeepPartial مفيد بشكل خاص لعمليات التحديث حيث تريد السماح بتحديثات جزئية للكائنات المتداخلة بعمق دون طلب جميع الخصائص على كل مستوى.
DeepReadonly - عدم التغيير المتكرر
اجعل جميع الخصائص على جميع مستويات التداخل للقراءة فقط لفرض عدم التغيير الكامل:
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Array<infer U>
? ReadonlyArray<DeepReadonly<U>>
: DeepReadonly<T[P]>
: T[P];
};
// مثال الاستخدام
interface Config {
api: {
baseURL: string;
timeout: number;
headers: {
authorization: string;
contentType: string;
};
};
features: {
darkMode: boolean;
analytics: boolean;
};
}
type ImmutableConfig = DeepReadonly<Config>;
const config: ImmutableConfig = {
api: {
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
authorization: 'Bearer token',
contentType: 'application/json',
},
},
features: {
darkMode: true,
analytics: false,
},
};
// خطأ TypeScript: لا يمكن التعيين إلى 'baseURL' لأنها خاصية للقراءة فقط
// config.api.baseURL = 'https://new-api.com';
// خطأ TypeScript: لا يمكن التعيين إلى 'darkMode' لأنها خاصية للقراءة فقط
// config.features.darkMode = false;
DeepRequired - خصائص مطلوبة متكررة
اجعل جميع الخصائص الاختيارية مطلوبة على جميع مستويات التداخل:
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepRequired<U>>
: DeepRequired<T[P]>
: T[P];
};
// مثال مع خصائص متداخلة اختيارية
interface PartialForm {
username?: string;
profile?: {
bio?: string;
social?: {
twitter?: string;
github?: string;
};
};
}
type CompleteForm = DeepRequired<PartialForm>;
// خطأ TypeScript: يجب توفير جميع الخصائص
const form: CompleteForm = {
username: 'john_doe',
profile: {
bio: 'مطور',
social: {
twitter: '@johndoe',
github: 'johndoe',
},
},
};
-? يزيل العلامة الاختيارية من الخصائص. هذا عكس ? الذي يضيف العلامة الاختيارية.
PathKeys - الوصول الآمن من حيث النوع لمسار الكائن
ولّد أنواع سلسلة حرفية تمثل جميع المسارات الصالحة عبر كائن متداخل:
type PathKeys<T> = T extends object
? {
[K in keyof T]: K extends string
? T[K] extends object
? `${K}` | `${K}.${PathKeys<T[K]>}`
: `${K}`
: never;
}[keyof T]
: never;
// مثال الاستخدام
interface Settings {
user: {
name: string;
email: string;
preferences: {
theme: string;
language: string;
};
};
app: {
version: string;
features: {
darkMode: boolean;
notifications: boolean;
};
};
}
type SettingPath = PathKeys<Settings>;
// نوع النتيجة:
// "user" | "user.name" | "user.email" | "user.preferences" |
// "user.preferences.theme" | "user.preferences.language" |
// "app" | "app.version" | "app.features" |
// "app.features.darkMode" | "app.features.notifications"
// دالة getter آمنة من حيث النوع
function getNestedValue<T, P extends PathKeys<T>>(
obj: T,
path: P
): any {
return path.split('.').reduce((acc: any, key) => acc?.[key], obj);
}
const settings: Settings = {
user: {
name: 'أحمد',
email: 'ahmad@example.com',
preferences: {
theme: 'dark',
language: 'ar',
},
},
app: {
version: '1.0.0',
features: {
darkMode: true,
notifications: false,
},
},
};
const theme = getNestedValue(settings, 'user.preferences.theme'); // صالح
// const invalid = getNestedValue(settings, 'user.invalid.path'); // خطأ TypeScript
Mutable - إزالة معدّلات Readonly
أنشئ نسخة قابلة للكتابة من نوع readonly:
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// نسخة عميقة
type DeepMutable<T> = {
-readonly [P in keyof T]: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepMutable<U>>
: DeepMutable<T[P]>
: T[P];
};
// مثال
interface ReadonlyUser {
readonly id: number;
readonly name: string;
readonly profile: {
readonly bio: string;
};
}
type WritableUser = DeepMutable<ReadonlyUser>;
const user: WritableUser = {
id: 1,
name: 'أحمد',
profile: { bio: 'مطور' },
};
user.name = 'فاطمة'; // مسموح
user.profile.bio = 'مصممة'; // مسموح
Mutable بحذر. أنواع Readonly موجودة لسبب - إزالة معدّلات readonly يمكن أن تعرض ضمانات عدم التغيير التي تعتمد عليها أجزاء أخرى من قاعدة الكود الخاصة بك للخطر.
PickByType - اختيار الخصائص حسب نوع القيمة
أنشئ نوعًا يحتوي فقط على الخصائص التي تطابق قيمها نوعًا محددًا:
type PickByType<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
// العكس: OmitByType
type OmitByType<T, U> = {
[P in keyof T as T[P] extends U ? never : P]: T[P];
};
// مثال الاستخدام
interface Product {
id: number;
name: string;
price: number;
description: string;
inStock: boolean;
rating: number;
category: string;
}
// احصل على خصائص السلسلة فقط
type StringProps = PickByType<Product, string>;
// النتيجة: { name: string; description: string; category: string }
// احصل على خصائص الرقم فقط
type NumberProps = PickByType<Product, number>;
// النتيجة: { id: number; price: number; rating: number }
// احصل على الخصائص غير السلسلة فقط
type NonStringProps = OmitByType<Product, string>;
// النتيجة: { id: number; price: number; inStock: boolean; rating: number }
RequireAtLeastOne - طلب واحدة من عدة خصائص
تأكد من توفير خاصية واحدة على الأقل من مجموعة:
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
T,
Exclude<keyof T, Keys>
> &
{
[K in Keys]-?: Required<Pick<T, K>> &
Partial<Pick<T, Exclude<Keys, K>>>;
}[Keys];
// مثال الاستخدام
interface ContactInfo {
name: string;
email?: string;
phone?: string;
address?: string;
}
type ContactRequired = RequireAtLeastOne<
ContactInfo,
'email' | 'phone' | 'address'
>;
// صالح: يحتوي على البريد الإلكتروني
const contact1: ContactRequired = {
name: 'أحمد',
email: 'ahmad@example.com',
};
// صالح: يحتوي على الهاتف
const contact2: ContactRequired = {
name: 'فاطمة',
phone: '555-0123',
};
// صالح: يحتوي على متعددة
const contact3: ContactRequired = {
name: 'محمد',
email: 'mohamed@example.com',
phone: '555-0456',
};
// خطأ TypeScript: يجب أن يحتوي على واحدة على الأقل من email أو phone أو address
// const invalid: ContactRequired = {
// name: 'غير صالح',
// };
RequireExactlyOne - طلب خاصية واحدة بالضبط
تأكد من توفير خاصية واحدة بالضبط من مجموعة (خصائص حصرية متبادلة):
type RequireExactlyOne<T, Keys extends keyof T = keyof T> = Pick<
T,
Exclude<keyof T, Keys>
> &
{
[K in Keys]-?: Required<Pick<T, K>> &
Partial<Record<Exclude<Keys, K>, undefined>>;
}[Keys];
// مثال الاستخدام
interface SearchParams {
query?: string;
userId?: number;
email?: string;
}
type ExactSearch = RequireExactlyOne<
SearchParams,
'query' | 'userId' | 'email'
>;
// صالح: خاصية واحدة بالضبط
const search1: ExactSearch = { query: 'typescript' };
const search2: ExactSearch = { userId: 123 };
const search3: ExactSearch = { email: 'user@example.com' };
// خطأ TypeScript: لا يمكن توفير عدة خصائص
// const invalid1: ExactSearch = { query: 'ts', userId: 1 };
// خطأ TypeScript: يجب توفير خاصية واحدة بالضبط
// const invalid2: ExactSearch = {};
Prettify - تسطيح أنواع التقاطع لعرض أفضل في IDE
أنواع التقاطع في TypeScript يمكن أن يكون من الصعب قراءتها في تلميحات IDE. Prettify يسطحها:
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
// مثال
type Base = { id: number; name: string };
type Extended = Base & { email: string; age: number };
// بدون Prettify، يعرض IDE: Base & { email: string; age: number }
type Ugly = Extended;
// مع Prettify، يعرض IDE: { id: number; name: string; email: string; age: number }
type Pretty = Prettify<Extended>;
Prettify عند إنشاء أنواع أدوات مساعدة معقدة لجعل تلميحات IDE أكثر قابلية للقراءة. لا يغير سلوك النوع، فقط كيفية عرضه.
PartialBy - جعل خصائص محددة اختيارية
اجعل خصائص محددة فقط اختيارية مع الحفاظ على الخصائص الأخرى مطلوبة:
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// مثال
interface User {
id: number;
email: string;
username: string;
firstName: string;
lastName: string;
}
// اجعل البريد الإلكتروني واسم المستخدم فقط اختياريًا
type UserUpdate = PartialBy<User, 'email' | 'username'>;
const update: UserUpdate = {
id: 1,
firstName: 'أحمد',
lastName: 'علي',
// email و username اختياريان
};
بناء مكتبة شاملة لأنواع الأدوات المساعدة
اجمع الأدوات المساعدة في مكتبة قابلة لإعادة الاستخدام:
// types/utils.ts
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: DeepPartial<T[P]>
: T[P];
};
export type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Array<infer U>
? ReadonlyArray<DeepReadonly<U>>
: DeepReadonly<T[P]>
: T[P];
};
export type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepRequired<U>>
: DeepRequired<T[P]>
: T[P];
};
export type DeepMutable<T> = {
-readonly [P in keyof T]: T[P] extends object
? T[P] extends Array<infer U>
? Array<DeepMutable<U>>
: DeepMutable<T[P]>
: T[P];
};
export type PickByType<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
export type OmitByType<T, U> = {
[P in keyof T as T[P] extends U ? never : P]: T[P];
};
export type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
export type PartialBy<T, K extends keyof T> = Omit<T, K> &
Partial<Pick<T, K>>;
export type RequiredBy<T, K extends keyof T> = Omit<T, K> &
Required<Pick<T, K>>;
// الاستخدام عبر تطبيقك
import { DeepPartial, DeepReadonly, Prettify } from './types/utils';
- نفذ أداة مساعدة
DeepNonNullable<T>تزيلnullوundefinedمن جميع الخصائص بشكل متكرر - أنشئ أداة مساعدة
ValueOf<T>تستخرج جميع أنواع القيم الممكنة من نوع كائن - ابنِ
StrictOmit<T, K>يثير خطأ ترجمة إذا كانت K تحتوي على مفاتيح ليست في T (على عكس Omit المدمج) - نفذ
PathValue<T, P>يعيد النوع في مسار سلسلة محدد (مثل "user.profile.name") - أنشئ أداة مساعدة
PromisifyMethods<T>تلف جميع أنواع إرجاع الطرق في Promises - ابنِ مكتبة أنواع أدوات مساعدة شاملة مع 10 أدوات مساعدة مخصصة على الأقل واستخدمها في نموذج بيانات من العالم الحقيقي
ملخص
في هذا الدرس، استكشفت أنماط أنواع الأدوات المساعدة المتقدمة التي توسع نظام النوع في TypeScript للتعامل مع سيناريوهات العالم الحقيقي المعقدة. تعلمت بناء DeepPartial و DeepReadonly والأدوات المساعدة المتكررة الأخرى، وإنشاء محددات مسار كائن آمنة من حيث النوع مع PathKeys، واختيار الخصائص حسب النوع، وفرض قيود "واحدة على الأقل" و "واحدة بالضبط" على الخصائص، وتسطيح أنواع التقاطع لقابلية قراءة أفضل. تمكّنك هذه الأنماط من إنشاء أدوات نوع متطورة وقابلة لإعادة الاستخدام تقلل من الكود الزائد، وتفرض قيودًا معقدة، وتجعل قاعدة الكود الخاصة بك أكثر قابلية للصيانة وأمانًا من حيث النوع.