العربية

أتقن فحوصات الخصائص الزائدة في TypeScript لمنع أخطاء وقت التشغيل وتعزيز أمان أنواع الكائنات لتطبيقات JavaScript قوية ويمكن التنبؤ بها.

فحوصات الخصائص الزائدة في TypeScript: تعزيز أمان أنواع الكائنات لديك

في عالم تطوير البرمجيات الحديث، خاصة مع JavaScript، يعد ضمان سلامة وإمكانية التنبؤ بالكود الخاص بك أمرًا بالغ الأهمية. بينما توفر JavaScript مرونة هائلة، إلا أنها قد تؤدي أحيانًا إلى أخطاء وقت التشغيل بسبب هياكل البيانات غير المتوقعة أو عدم تطابق الخصائص. وهنا يبرز دور TypeScript، حيث يوفر إمكانيات الكتابة الثابتة (static typing) التي تلتقط العديد من الأخطاء الشائعة قبل أن تظهر في بيئة الإنتاج. واحدة من أقوى ميزات TypeScript، والتي يساء فهمها أحيانًا، هي فحص الخصائص الزائدة (excess property check).

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

فهم المفهوم الأساسي: ما هي فحوصات الخصائص الزائدة؟

في جوهرها، فحص الخصائص الزائدة في TypeScript هو آلية مترجم (compiler) تمنعك من تعيين كائن حرفي (object literal) إلى متغير لا يسمح نوعه صراحة بتلك الخصائص الإضافية. بعبارات أبسط، إذا قمت بتعريف كائن حرفي وحاولت تعيينه إلى متغير له تعريف نوع محدد (مثل واجهة أو اسم مستعار للنوع)، وكان هذا الكائن الحرفي يحتوي على خصائص غير معلنة في النوع المحدد، فسيقوم TypeScript بالإبلاغ عنه كخطأ أثناء عملية التصريف (compilation).

لنوضح ذلك بمثال أساسي:


interface User {
  name: string;
  age: number;
}

const newUser: User = {
  name: 'Alice',
  age: 30,
  email: 'alice@example.com' // خطأ: الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'email' غير موجود في النوع 'User'.
};

في هذا المقتطف، نعرّف `interface` تسمى `User` بخاصيتين: `name` و `age`. عندما نحاول إنشاء كائن حرفي بخاصية إضافية، `email`، وتعيينه إلى متغير من نوع `User`، يكتشف TypeScript على الفور عدم التطابق. خاصية `email` هي خاصية 'زائدة' لأنها غير محددة في واجهة `User`. يتم إجراء هذا الفحص تحديدًا عند استخدام كائن حرفي للتعيين.

لماذا تعتبر فحوصات الخصائص الزائدة مهمة؟

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

متى تُطبّق فحوصات الخصائص الزائدة؟

من الأهمية بمكان فهم الظروف المحددة التي يقوم فيها TypeScript بإجراء هذه الفحوصات. يتم تطبيقها بشكل أساسي على الكائنات الحرفية (object literals) عند تعيينها إلى متغير أو تمريرها كوسيط (argument) لدالة.

السيناريو 1: تعيين الكائنات الحرفية للمتغيرات

كما رأينا في مثال `User` أعلاه، فإن التعيين المباشر لكائن حرفي بخصائص إضافية إلى متغير محدد النوع يؤدي إلى تفعيل الفحص.

السيناريو 2: تمرير الكائنات الحرفية للدوال

عندما تتوقع دالة وسيطًا من نوع معين، وتقوم بتمرير كائن حرفي يحتوي على خصائص زائدة، سيقوم TypeScript بالإبلاغ عن ذلك.


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

function displayProduct(product: Product): void {
  console.log(`Product ID: ${product.id}, Name: ${product.name}`);
}

displayProduct({
  id: 101,
  name: 'Laptop',
  price: 1200 // خطأ: وسيط من نوع '{ id: number; name: string; price: number; }' غير قابل للتعيين إلى معامل من نوع 'Product'.
             // الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'price' غير موجود في النوع 'Product'.
});

هنا، خاصية `price` في الكائن الحرفي الذي تم تمريره إلى `displayProduct` هي خاصية زائدة، حيث أن واجهة `Product` لا تعرفها.

متى *لا* تُطبّق فحوصات الخصائص الزائدة؟

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

1. عند عدم استخدام الكائنات الحرفية للتعيين

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


interface Config {
  timeout: number;
}

function setupConfig(config: Config) {
  console.log(`Timeout set to: ${config.timeout}`);
}

const userProvidedConfig = {
  timeout: 5000,
  retries: 3 // هذه الخاصية 'retries' هي خاصية زائدة وفقًا لـ 'Config'
};

setupConfig(userProvidedConfig); // لا يوجد خطأ!

// على الرغم من أن userProvidedConfig لديه خاصية إضافية، إلا أنه يتم تخطي الفحص
// لأنه ليس كائنًا حرفيًا يتم تمريره مباشرة.
// يتحقق TypeScript من نوع userProvidedConfig نفسه.
// إذا تم الإعلان عن userProvidedConfig بنوع Config، لكان الخطأ قد حدث في وقت سابق.
// ومع ذلك، إذا تم الإعلان عنه كـ 'any' أو نوع أوسع، يتم تأجيل الخطأ.

// طريقة أكثر دقة لإظهار التجاوز:
let anotherConfig;

if (Math.random() > 0.5) {
  anotherConfig = {
    timeout: 1000,
    host: 'localhost' // خاصية زائدة
  };
} else {
  anotherConfig = {
    timeout: 2000,
    port: 8080 // خاصية زائدة
  };
}

setupConfig(anotherConfig as Config); // لا يوجد خطأ بسبب تأكيد النوع والتجاوز

// المفتاح هو أن 'anotherConfig' ليس كائنًا حرفيًا عند نقطة التعيين إلى setupConfig.
// لو كان لدينا متغير وسيط من نوع 'Config'، لفشل التعيين الأولي.

// مثال على متغير وسيط:
let intermediateConfig: Config;

intermediateConfig = {
  timeout: 3000,
  logging: true // خطأ: الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'logging' غير موجود في النوع 'Config'.
};

في أول مثال `setupConfig(userProvidedConfig)`، `userProvidedConfig` هو متغير يحمل كائنًا. يتحقق TypeScript مما إذا كان `userProvidedConfig` ككل يتوافق مع النوع `Config`. لا يطبق الفحص الصارم للكائن الحرفي على `userProvidedConfig` نفسه. إذا تم الإعلان عن `userProvidedConfig` بنوع لا يتطابق مع `Config`، لكان الخطأ قد حدث أثناء إعلانه أو تعيينه. يحدث التجاوز لأن الكائن قد تم إنشاؤه بالفعل وتعيينه لمتغير قبل تمريره إلى الدالة.

2. تأكيدات النوع (Type Assertions)

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


interface Settings {
  theme: 'dark' | 'light';
}

const mySettings = {
  theme: 'dark',
  fontSize: 14 // خاصية زائدة
} as Settings;

// لا يوجد خطأ هنا بسبب تأكيد النوع.
// نحن نخبر TypeScript: "ثق بي، هذا الكائن يتوافق مع Settings."
console.log(mySettings.theme);
// console.log(mySettings.fontSize); // قد يتسبب هذا في خطأ وقت التشغيل إذا لم تكن fontSize موجودة بالفعل.

3. استخدام توقيعات الفهرس (Index Signatures) أو صيغة النشر (Spread Syntax) في تعريفات النوع

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

استخدام توقيعات الفهرس:


interface FlexibleObject {
  id: number;
  [key: string]: any; // يسمح بأي مفتاح نصي بأي قيمة
}

const flexibleItem: FlexibleObject = {
  id: 1,
  name: 'Widget',
  version: '1.0.0'
};

// لا يوجد خطأ لأن 'name' و 'version' مسموح بهما بواسطة توقيع الفهرس.
console.log(flexibleItem.name);

استخدام صيغة النشر في تعريفات النوع (أقل شيوعًا لتجاوز الفحوصات مباشرة، وأكثر لتعريف الأنواع المتوافقة):

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

4. استخدام `Object.assign()` أو صيغة النشر لدمج الكائنات

عندما تستخدم `Object.assign()` أو صيغة النشر (`...`) لدمج الكائنات، يتصرف فحص الخصائص الزائدة بشكل مختلف. ينطبق على الكائن الحرفي الناتج الذي يتم تشكيله.


interface BaseConfig {
  host: string;
}

interface ExtendedConfig extends BaseConfig {
  port: number;
}

const defaultConfig: BaseConfig = {
  host: 'localhost'
};

const userConfig = {
  port: 8080,
  timeout: 5000 // خاصية زائدة بالنسبة لـ BaseConfig، ولكنها متوقعة من قبل النوع المدمج
};

// النشر في كائن حرفي جديد يتوافق مع ExtendedConfig
const finalConfig: ExtendedConfig = {
  ...defaultConfig,
  ...userConfig
};

// هذا مقبول بشكل عام لأن 'finalConfig' تم الإعلان عنه كـ 'ExtendedConfig'
// والخصائص تتطابق. الفحص يتم على نوع 'finalConfig'.

// لننظر في سيناريو حيث *قد* يفشل:

interface SmallConfig {
  key: string;
}

const data1 = { key: 'abc', value: 123 }; // 'value' زائدة هنا
const data2 = { key: 'xyz', status: 'active' }; // 'status' زائدة هنا

// محاولة التعيين إلى نوع لا يستوعب الإضافات

// const combined: SmallConfig = {
//   ...data1, // خطأ: الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'value' غير موجود في النوع 'SmallConfig'.
//   ...data2  // خطأ: الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'status' غير موجود في النوع 'SmallConfig'.
// };

// يحدث الخطأ لأن الكائن الحرفي الذي تم تشكيله بواسطة صيغة النشر
// يحتوي على خصائص ('value', 'status') غير موجودة في 'SmallConfig'.

// إذا أنشأنا متغيرًا وسيطًا بنوع أوسع:

const temp: any = {
  ...data1,
  ...data2
};

// ثم التعيين إلى SmallConfig، يتم تجاوز فحص الخصائص الزائدة عند إنشاء الكائن الحرفي الأولي،
// ولكن فحص النوع عند التعيين قد لا يزال يحدث إذا تم استنتاج نوع temp بشكل أكثر صرامة.
// ومع ذلك، إذا كان temp هو 'any'، فلن يحدث أي فحص حتى التعيين إلى 'combined'.

// لنقم بتوضيح فهم النشر مع فحوصات الخصائص الزائدة:
// يحدث الفحص عندما يتم تعيين الكائن الحرفي الذي تم إنشاؤه بواسطة صيغة النشر
// إلى متغير أو تمريره إلى دالة تتوقع نوعًا أكثر تحديدًا.

interface SpecificShape { 
  id: number;
}

const objA = { id: 1, extra1: 'hello' };
const objB = { id: 2, extra2: 'world' };

// سيفشل هذا إذا لم يسمح SpecificShape بـ 'extra1' أو 'extra2':
// const merged: SpecificShape = {
//   ...objA,
//   ...objB
// };

// سبب الفشل هو أن صيغة النشر تنشئ فعليًا كائنًا حرفيًا جديدًا.
// إذا كان لدى objA و objB مفاتيح متداخلة، فسيفوز الأخير. يرى المترجم
// هذا الكائن الحرفي الناتج ويتحقق منه مقابل 'SpecificShape'.

// لجعله يعمل، قد تحتاج إلى خطوة وسيطة أو نوع أكثر تساهلاً:

const tempObj = {
  ...objA,
  ...objB
};

// الآن، إذا كان لدى tempObj خصائص غير موجودة في SpecificShape، فسيفشل التعيين:
// const mergedCorrected: SpecificShape = tempObj; // خطأ: الكائن الحرفي قد يحدد فقط الخصائص المعروفة...

// المفتاح هو أن المترجم يحلل شكل الكائن الحرفي الذي يتم تشكيله.
// إذا كان هذا الكائن الحرفي يحتوي على خصائص غير محددة في النوع المستهدف، فهذا خطأ.

// حالة الاستخدام النموذجية لصيغة النشر مع فحوصات الخصائص الزائدة:

interface UserProfile {
  userId: string;
  username: string;
}

interface AdminProfile extends UserProfile {
  adminLevel: number;
}

const baseUserData: UserProfile = {
  userId: 'user-123',
  username: 'coder'
};

const adminData = {
  adminLevel: 5,
  lastLogin: '2023-10-27'
};

// هذا هو المكان الذي يكون فيه فحص الخصائص الزائدة ذا صلة:
// const adminProfile: AdminProfile = {
//   ...baseUserData,
//   ...adminData // خطأ: الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'lastLogin' غير موجود في النوع 'AdminProfile'.
// };

// الكائن الحرفي الذي تم إنشاؤه بواسطة النشر يحتوي على 'lastLogin'، والذي ليس في 'AdminProfile'.
// لإصلاح ذلك، يجب أن يتوافق 'adminData' بشكل مثالي مع AdminProfile أو يجب التعامل مع الخاصية الزائدة.

// النهج المصحح:
const validAdminData = {
  adminLevel: 5
};

const adminProfileCorrect: AdminProfile = {
  ...baseUserData,
  ...validAdminData
};

console.log(adminProfileCorrect.userId);
console.log(adminProfileCorrect.adminLevel);

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

استراتيجيات للتعامل مع الخصائص الزائدة

بينما تكون فحوصات الخصائص الزائدة مفيدة، هناك سيناريوهات مشروعة قد يكون لديك فيها خصائص إضافية تريد تضمينها أو معالجتها بشكل مختلف. إليك استراتيجيات شائعة:

1. خصائص البقية (Rest Properties) مع الأسماء المستعارة للأنواع أو الواجهات

يمكنك استخدام صيغة معامل البقية (`...rest`) ضمن الأسماء المستعارة للأنواع أو الواجهات لالتقاط أي خصائص متبقية غير محددة صراحة. هذه طريقة نظيفة للاعتراف بهذه الخصائص الزائدة وجمعها.


interface UserProfile {
  id: number;
  name: string;
}

interface UserWithMetadata extends UserProfile {
  metadata: {
    [key: string]: any;
  };
}

// أو بشكل أكثر شيوعًا مع اسم مستعار للنوع وصيغة البقية:
type UserProfileWithMetadata = UserProfile & {
  [key: string]: any;
};

const user1: UserProfileWithMetadata = {
  id: 1,
  name: 'Bob',
  email: 'bob@example.com',
  isAdmin: true
};

// لا يوجد خطأ، حيث يتم التقاط 'email' و 'isAdmin' بواسطة توقيع الفهرس في UserProfileWithMetadata.
console.log(user1.email);
console.log(user1.isAdmin);

// طريقة أخرى باستخدام معاملات البقية في تعريف النوع:
interface ConfigWithRest {
  apiUrl: string;
  timeout?: number;
  // التقاط جميع الخصائص الأخرى في 'extraConfig'
  [key: string]: any;
}

const appConfig: ConfigWithRest = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  featureFlags: {
    newUI: true,
    betaFeatures: false
  }
};

console.log(appConfig.featureFlags);

استخدام `[key: string]: any;` أو توقيعات فهرس مشابهة هو الطريقة الاصطلاحية للتعامل مع الخصائص الإضافية العشوائية.

2. التفكيك (Destructuring) مع صيغة البقية

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


interface Employee {
  employeeId: string;
  department: string;
}

function processEmployeeData(data: Employee & { [key: string]: any }) {
  const { employeeId, department, ...otherDetails } = data;

  console.log(`Employee ID: ${employeeId}`);
  console.log(`Department: ${department}`);
  console.log('Other details:', otherDetails);
  // otherDetails ستحتوي على أي خصائص لم يتم تفكيكها صراحة،
  // مثل 'salary', 'startDate', إلخ.
}

const employeeInfo = {
  employeeId: 'emp-789',
  department: 'Engineering',
  salary: 90000,
  startDate: '2022-01-15'
};

processEmployeeData(employeeInfo);

// حتى لو كان لدى employeeInfo خاصية إضافية في البداية، يتم تجاوز فحص الخصائص الزائدة
// إذا كان توقيع الدالة يقبلها (على سبيل المثال، باستخدام توقيع الفهرس).
// إذا تم تحديد نوع processEmployeeData بشكل صارم كـ 'Employee'، وكان لدى employeeInfo 'salary'،
// فسيحدث خطأ إذا كان employeeInfo كائنًا حرفيًا تم تمريره مباشرة.
// ولكن هنا، employeeInfo هو متغير، ونوع الدالة يتعامل مع الإضافات.

3. تحديد جميع الخصائص صراحة (إذا كانت معروفة)

إذا كنت تعرف الخصائص الإضافية المحتملة، فإن أفضل نهج هو إضافتها إلى الواجهة أو الاسم المستعار للنوع. هذا يوفر أقصى درجات أمان النوع.


interface UserProfile {
  id: number;
  name: string;
  email?: string; // بريد إلكتروني اختياري
}

const userWithEmail: UserProfile = {
  id: 2,
  name: 'Charlie',
  email: 'charlie@example.com'
};

const userWithoutEmail: UserProfile = {
  id: 3,
  name: 'David'
};

// إذا حاولنا إضافة خاصية غير موجودة في UserProfile:
// const userWithExtra: UserProfile = {
//   id: 4,
//   name: 'Eve',
//   phoneNumber: '555-1234'
// }; // خطأ: الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'phoneNumber' غير موجود في النوع 'UserProfile'.

4. استخدام `as` لتأكيدات النوع (بحذر)

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


interface ProductConfig {
  id: string;
  version: string;
}

// تخيل أن هذا يأتي من مصدر خارجي أو وحدة أقل صرامة
const externalConfig = {
  id: 'prod-abc',
  version: '1.2',
  debugMode: true // خاصية زائدة
};

// إذا كنت تعلم أن 'externalConfig' سيحتوي دائمًا على 'id' و 'version' وتريد التعامل معه كـ ProductConfig:
const productConfig = externalConfig as ProductConfig;

// هذا التأكيد يتجاوز فحص الخصائص الزائدة على `externalConfig` نفسه.
// ومع ذلك، إذا كنت ستمرر كائنًا حرفيًا مباشرة:

// const productConfigLiteral: ProductConfig = {
//   id: 'prod-xyz',
//   version: '2.0',
//   debugMode: false
// }; // خطأ: الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'debugMode' غير موجود في النوع 'ProductConfig'.

5. حراس النوع (Type Guards)

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


interface Shape {
  kind: 'circle' | 'square';
}

interface Circle extends Shape {
  kind: 'circle';
  radius: number;
}

interface Square extends Shape {
  kind: 'square';
  sideLength: number;
}

function calculateArea(shape: Shape) {
  if (shape.kind === 'circle') {
    // يعرف TypeScript أن 'shape' هو Circle هنا
    console.log(Math.PI * shape.radius ** 2);
  } else if (shape.kind === 'square') {
    // يعرف TypeScript أن 'shape' هو Square هنا
    console.log(shape.sideLength ** 2);
  }
}

const circleData = {
  kind: 'circle' as const, // استخدام 'as const' لاستنتاج النوع الحرفي
  radius: 10,
  color: 'red' // خاصية زائدة
};

// عند تمريره إلى calculateArea، يتوقع توقيع الدالة 'Shape'.
// ستقوم الدالة نفسها بالوصول إلى 'kind' بشكل صحيح.
// إذا كانت calculateArea تتوقع 'Circle' مباشرة وتلقت circleData
// ككائن حرفي، ستكون 'color' مشكلة.

// لنوضح فحص الخصائص الزائدة مع دالة تتوقع نوعًا فرعيًا محددًا:

function processCircle(circle: Circle) {
  console.log(`Processing circle with radius: ${circle.radius}`);
}

// processCircle(circleData); // خطأ: وسيط من نوع '{ kind: "circle"; radius: number; color: string; }' غير قابل للتعيين إلى معامل من نوع 'Circle'.
                         // الكائن الحرفي قد يحدد فقط الخصائص المعروفة، و 'color' غير موجود في النوع 'Circle'.

// لإصلاح ذلك، يمكنك التفكيك أو استخدام نوع أكثر تساهلاً لـ circleData:

const { color, ...circleDataWithoutColor } = circleData;
processCircle(circleDataWithoutColor);

// أو تعريف circleData لتشمل نوعًا أوسع:

const circleDataWithExtras: Circle & { [key: string]: any } = {
  kind: 'circle',
  radius: 15,
  color: 'blue'
};
processCircle(circleDataWithExtras); // الآن يعمل.

المآزق الشائعة وكيفية تجنبها

حتى المطورون ذوو الخبرة يمكن أن يتفاجأوا أحيانًا بفحوصات الخصائص الزائدة. إليك بعض المآزق الشائعة:

اعتبارات عالمية وأفضل الممارسات

عند العمل في بيئة تطوير عالمية ومتنوعة، يعد الالتزام بممارسات متسقة حول أمان النوع أمرًا بالغ الأهمية:

الخاتمة

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

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

النقاط الرئيسية:

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