العربية

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

إتقان أنواع TypeScript المساعدة: دليل عملي للمطورين العالميين

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

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

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

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

Partial<T>

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

مثال:

تخيل أنك تبني منصة تجارة إلكترونية مع عملاء من مناطق متنوعة. لديك نوع Customer:


interface Customer {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  phoneNumber: string;
  address: {
    street: string;
    city: string;
    country: string;
    postalCode: string;
  };
  preferences?: {
    language: string;
    currency: string;
  }
}

عند تحديث معلومات العميل، قد لا ترغب في طلب جميع الحقول. يسمح لك Partial<Customer> بتعريف نوع تكون فيه جميع خصائص Customer اختيارية:


type PartialCustomer = Partial<Customer>;

function updateCustomer(id: string, updates: PartialCustomer): void {
  // ... التنفيذ لتحديث العميل بالمعرف المحدد
}

updateCustomer("123", { firstName: "John", lastName: "Doe" }); // صالح
updateCustomer("456", { address: { city: "London" } }); // صالح

Readonly<T>

يقوم Readonly<T> بإنشاء نوع تكون فيه جميع خصائص T للقراءة فقط (readonly)، مما يمنع التعديل بعد التهيئة. هذا قيم لضمان عدم القابلية للتغيير.

مثال:

خذ بعين الاعتبار كائن تكوين لتطبيقك العالمي:


interface AppConfig {
  apiUrl: string;
  theme: string;
  supportedLanguages: string[];
  version: string; // تمت إضافة الإصدار
}

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  theme: "dark",
  supportedLanguages: ["en", "fr", "de", "es", "zh"],
  version: "1.0.0"
};

لمنع التعديل العرضي للتكوين بعد التهيئة، يمكنك استخدام Readonly<AppConfig>:


type ReadonlyAppConfig = Readonly<AppConfig>;

const readonlyConfig: ReadonlyAppConfig = {
  apiUrl: "https://api.example.com",
  theme: "dark",
  supportedLanguages: ["en", "fr", "de", "es", "zh"],
  version: "1.0.0"
};

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

Pick<T, K>

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

مثال:

لنفترض أن لديك واجهة Event بخصائص متنوعة:


interface Event {
  id: string;
  title: string;
  description: string;
  location: string;
  startTime: Date;
  endTime: Date;
  organizer: string;
  attendees: string[];
}

إذا كنت تحتاج فقط إلى title و location و startTime لمكون عرض معين، يمكنك استخدام Pick:


type EventSummary = Pick<Event, "title" | "location" | "startTime">;

function displayEventSummary(event: EventSummary): void {
  console.log(`Event: ${event.title} at ${event.location} on ${event.startTime}`);
}

Omit<T, K>

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

مثال:

باستخدام نفس واجهة Event، إذا كنت ترغب في إنشاء نوع لإنشاء أحداث جديدة، فقد ترغب في استبعاد خاصية id، والتي يتم إنشاؤها عادةً بواسطة الواجهة الخلفية:


type NewEvent = Omit<Event, "id">;

function createEvent(event: NewEvent): void {
  // ... التنفيذ لإنشاء حدث جديد
}

Record<K, T>

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

مثال:

تخيل أنك بحاجة إلى تخزين ترجمات لواجهة مستخدم تطبيقك. يمكنك استخدام Record لتعريف نوع لترجماتك:


type Translations = Record<string, string>;

const enTranslations: Translations = {
  "hello": "Hello",
  "goodbye": "Goodbye",
  "welcome": "Welcome to our platform!"
};

const frTranslations: Translations = {
  "hello": "Bonjour",
  "goodbye": "Au revoir",
  "welcome": "Bienvenue sur notre plateforme !"
};

function translate(key: string, language: string): string {
  const translations = language === "en" ? enTranslations : frTranslations; // مبسط
  return translations[key] || key; // الرجوع إلى المفتاح إذا لم يتم العثور على ترجمة
}

console.log(translate("hello", "en")); // المخرجات: Hello
console.log(translate("hello", "fr")); // المخرجات: Bonjour
console.log(translate("nonexistent", "en")); // المخرجات: nonexistent

Exclude<T, U>

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

مثال:

قد يكون لديك نوع يمثل أنواعًا مختلفة من الأحداث:


type EventType = "concert" | "conference" | "workshop" | "webinar";

إذا كنت تريد إنشاء نوع يستبعد أحداث "webinar"، يمكنك استخدام Exclude:


type PhysicalEvent = Exclude<EventType, "webinar">;

// PhysicalEvent هو الآن "concert" | "conference" | "workshop"

function attendPhysicalEvent(event: PhysicalEvent): void {
  console.log(`Attending a ${event}`);
}

// attendPhysicalEvent("webinar"); // خطأ: وسيط من النوع '"webinar"' لا يمكن تعيينه لمعامل من النوع '"concert" | "conference" | "workshop"'.

attendPhysicalEvent("concert"); // صالح

Extract<T, U>

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

مثال:

باستخدام نفس EventType، يمكنك استخراج نوع حدث الندوة عبر الإنترنت:


type OnlineEvent = Extract<EventType, "webinar">;

// OnlineEvent هو الآن "webinar"

function attendOnlineEvent(event: OnlineEvent): void {
  console.log(`Attending a ${event} online`);
}

attendOnlineEvent("webinar"); // صالح
// attendOnlineEvent("concert"); // خطأ: وسيط من النوع '"concert"' لا يمكن تعيينه لمعامل من النوع '"webinar"'.

NonNullable<T>

يقوم NonNullable<T> بإنشاء نوع عن طريق استبعاد null و undefined من T.

مثال:


type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>;

// DefinitelyString هو الآن string

function processString(str: DefinitelyString): void {
  console.log(str.toUpperCase());
}

// processString(null); // خطأ: وسيط من النوع 'null' لا يمكن تعيينه لمعامل من النوع 'string'.
// processString(undefined); // خطأ: وسيط من النوع 'undefined' لا يمكن تعيينه لمعامل من النوع 'string'.
processString("hello"); // صالح

ReturnType<T>

يقوم ReturnType<T> بإنشاء نوع يتكون من نوع الإرجاع للدالة T.

مثال:


function greet(name: string): string {
  return `Hello, ${name}!`;
}

type Greeting = ReturnType<typeof greet>;

// Greeting هو الآن string

const message: Greeting = greet("World");

console.log(message);

Parameters<T>

يقوم Parameters<T> بإنشاء نوع tuple من أنواع معلمات نوع الدالة T.

مثال:


function logEvent(eventName: string, eventData: object): void {
  console.log(`Event: ${eventName}`, eventData);
}

type LogEventParams = Parameters<typeof logEvent>;

// LogEventParams هو الآن [eventName: string, eventData: object]

const params: LogEventParams = ["user_login", { userId: "123", timestamp: Date.now() }];

logEvent(...params);

ConstructorParameters<T>

يقوم ConstructorParameters<T> بإنشاء نوع tuple أو مصفوفة من أنواع معلمات نوع دالة البناء T. يستنتج أنواع الوسائط التي يجب تمريرها إلى دالة البناء لفئة ما.

مثال:


class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }
}


type GreeterParams = ConstructorParameters<typeof Greeter>;

// GreeterParams هو الآن [message: string]

const paramsGreeter: GreeterParams = ["World"];
const greeterInstance = new Greeter(...paramsGreeter);

console.log(greeterInstance.greet()); // المخرجات: Hello, World

Required<T>

يقوم Required<T> بإنشاء نوع يتكون من جميع خصائص T المعينة كإلزامية. يجعل جميع الخصائص الاختيارية إلزامية.

مثال:


interface UserProfile {
  name: string;
  age?: number;
  email?: string;
}

type RequiredUserProfile = Required<UserProfile>;

// RequiredUserProfile هو الآن { name: string; age: number; email: string; }

const completeProfile: RequiredUserProfile = {
  name: "Alice",
  age: 30,
  email: "alice@example.com"
};

// const incompleteProfile: RequiredUserProfile = { name: "Bob" }; // خطأ: خاصية 'age' مفقودة في النوع '{ name: string; }' ولكنها مطلوبة في النوع 'Required'.

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

أنواع القوالب الحرفية

تسمح لك أنواع القوالب الحرفية بإنشاء أنواع سلاسل نصية حرفية جديدة عن طريق ربط أنواع السلاسل النصية الحرفية الموجودة، وأنواع الأرقام الحرفية، والمزيد. يتيح هذا إمكانية معالجة الأنواع القوية المستندة إلى السلاسل النصية.

مثال:


type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/users` | `/api/products`;

type RequestURL = `${HTTPMethod} ${APIEndpoint}`;

// RequestURL هو الآن "GET /api/users" | "POST /api/users" | "PUT /api/users" | "DELETE /api/users" | "GET /api/products" | "POST /api/products" | "PUT /api/products" | "DELETE /api/products"

function makeRequest(url: RequestURL): void {
  console.log(`Making request to ${url}`);
}

makeRequest("GET /api/users"); // صالح
// makeRequest("INVALID /api/users"); // خطأ

الأنواع الشرطية

تسمح لك الأنواع الشرطية بتعريف أنواع تعتمد على شرط يتم التعبير عنه كعلاقة بين الأنواع. تستخدم الكلمة المفتاحية infer لاستخراج معلومات النوع.

مثال:


type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

// إذا كان T هو Promise، فإن النوع هو U؛ وإلا، فإن النوع هو T.

async function fetchData(): Promise<number> {
  return 42;
}


type Data = UnwrapPromise<ReturnType<typeof fetchData>>;

// Data هو الآن number

function processData(data: Data): void {
  console.log(data * 2);
}

processData(await fetchData());

التطبيقات العملية والسيناريوهات الواقعية

دعنا نستكشف سيناريوهات أكثر تعقيدًا من العالم الحقيقي حيث تتألق الأنواع المساعدة.

1. معالجة النماذج

عند التعامل مع النماذج، غالبًا ما تواجه سيناريوهات تحتاج فيها إلى تمثيل قيم النموذج الأولية، وقيم النموذج المحدثة، والقيم النهائية المقدمة. يمكن أن تساعدك الأنواع المساعدة في إدارة هذه الحالات المختلفة بكفاءة.


interface FormData {
  firstName: string;
  lastName: string;
  email: string;
  country: string; // مطلوب
  city?: string; // اختياري
  postalCode?: string;
  newsletterSubscription?: boolean;
}

// قيم النموذج الأولية (حقول اختيارية)
type InitialFormValues = Partial<FormData>;

// قيم النموذج المحدثة (قد تكون بعض الحقول مفقودة)
type UpdatedFormValues = Partial<FormData>;

// الحقول المطلوبة للإرسال
type RequiredForSubmission = Required<Pick<FormData, 'firstName' | 'lastName' | 'email' | 'country'>>;

// استخدم هذه الأنواع في مكونات النموذج الخاصة بك
function initializeForm(initialValues: InitialFormValues): void { }
function updateForm(updates: UpdatedFormValues): void {}
function submitForm(data: RequiredForSubmission): void {}


const initialForm: InitialFormValues = { newsletterSubscription: true };

const updateFormValues: UpdatedFormValues = {
    firstName: "John",
    lastName: "Doe"
};

// const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test" }; // خطأ: 'country' مفقود 
const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test", country: "USA" }; // موافق


2. تحويل بيانات API

عند استهلاك البيانات من واجهة برمجة التطبيقات (API)، قد تحتاج إلى تحويل البيانات إلى تنسيق مختلف لتطبيقك. يمكن أن تساعدك الأنواع المساعدة في تحديد بنية البيانات المحولة.


interface APIResponse {
  user_id: string;
  first_name: string;
  last_name: string;
  email_address: string;
  profile_picture_url: string;
  is_active: boolean;
}

// تحويل استجابة API إلى تنسيق أكثر قابلية للقراءة
type UserData = {
  id: string;
  fullName: string;
  email: string;
  avatar: string;
  active: boolean;
};

function transformApiResponse(response: APIResponse): UserData {
  return {
    id: response.user_id,
    fullName: `${response.first_name} ${response.last_name}`,
    email: response.email_address,
    avatar: response.profile_picture_url,
    active: response.is_active
  };
}

function fetchAndTransformData(url: string): Promise<UserData> {
    return fetch(url)
        .then(response => response.json())
        .then(data => transformApiResponse(data));
}


// يمكنك حتى فرض النوع عن طريق:

function saferTransformApiResponse(response: APIResponse): UserData {
    const {user_id, first_name, last_name, email_address, profile_picture_url, is_active} = response;
    const transformed: UserData = {
        id: user_id,
        fullName: `${first_name} ${last_name}`,
        email: email_address,
        avatar: profile_picture_url,
        active: is_active
    };

    return transformed;
}

3. التعامل مع كائنات التكوين

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


interface AppSettings {
  theme: "light" | "dark";
  language: string;
  notificationsEnabled: boolean;
  apiUrl?: string; // عنوان URL اختياري لواجهة برمجة التطبيقات لبيئات مختلفة
  timeout?: number;  //اختياري
}

// الإعدادات الافتراضية
const defaultSettings: AppSettings = {
  theme: "light",
  language: "en",
  notificationsEnabled: true
};

// دالة لدمج إعدادات المستخدم مع الإعدادات الافتراضية
function mergeSettings(userSettings: Partial<AppSettings>): AppSettings {
  return { ...defaultSettings, ...userSettings };
}

// استخدم الإعدادات المدمجة في تطبيقك
const mergedSettings = mergeSettings({ theme: "dark", apiUrl: "https://customapi.example.com" });
console.log(mergedSettings);

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

  • ابدأ ببساطة: ابدأ بالأنواع المساعدة الأساسية مثل Partial و Readonly قبل الانتقال إلى الأنواع الأكثر تعقيدًا.
  • استخدم أسماء وصفية: أعطِ أسماء ذات معنى لأسماء الأنواع المستعارة لتحسين القراءة.
  • اجمع بين الأنواع المساعدة: يمكنك دمج أنواع مساعدة متعددة لتحقيق تحويلات أنواع معقدة.
  • استفد من دعم المحرر: استفد من دعم المحرر الممتاز في TypeScript لاستكشاف تأثيرات الأنواع المساعدة.
  • افهم المفاهيم الأساسية: يعد الفهم القوي لنظام أنواع TypeScript ضروريًا للاستخدام الفعال للأنواع المساعدة.

الخاتمة

تُعد أنواع TypeScript المساعدة أدوات قوية يمكنها تحسين جودة الكود الخاص بك وقابليته للصيانة بشكل كبير. من خلال فهم وتطبيق هذه الأنواع المساعدة بفعالية، يمكنك كتابة تطبيقات أنظف وأكثر أمانًا من حيث النوع وأكثر قوة تلبي متطلبات مشهد التطوير العالمي. قدم هذا الدليل نظرة عامة شاملة على الأنواع المساعدة الشائعة وأمثلة عملية. جربها واستكشف إمكاناتها لتعزيز مشاريع TypeScript الخاصة بك. تذكر إعطاء الأولوية للقراءة والوضوح عند استخدام الأنواع المساعدة، والسعي دائمًا لكتابة كود يسهل فهمه وصيانته، بغض النظر عن مكان وجود زملائك المطورين.