مقدمة: لماذا لا يمكنك ببساطة تغيير النوع؟
\n\nتخيل الموقف التالي: أنت تستخدم مكتبة قوية للعمل مع الرسوم البيانية. لديها توثيق مثالي، ومئات النجوم على GitHub، وحتى ملفات .d.ts مع وصف الأنواع مرفقة. ولكن هناك مشكلة واحدة - قرر مطور المكتبة أن الحقل data يجب أن يكون من النوع string | number، بينما أنت بحاجة ماسة إلى النوع Date. تذهب إلى مجلد node_modules، وتعدل الملف - ثم ينهار كل شيء عند تنفيذ npm install التالي.
هل هذا مألوف؟ هذه واحدة من أكثر المشاكل شيوعًا التي يواجهها مطورو TypeScript. سؤال «تجاوز نوع خاصية واجهة معرفة في ملف Typescript d.ts» حصد آلاف المشاهدات على Stack Overflow. وليس من دون سبب - فالشيطان يكمن في التفاصيل.
\n\nفي هذه المقالة، سنستعرض 5 طرق قانونية لتجاوز نوع خاصية واجهة من ملف .d.ts خارجي. لا حيل مع نسخ الملفات - فقط TypeScript نقي.
فهم المشكلة: لماذا يقاوم TypeScript؟
\n\nقبل أن نخترق النظام، دعنا نفهم منطقه. TypeScript صارم مع الأنواع ليس من باب الخبث، بل من أجل أمانك. عندما تحاول تجاوز خاصية واجهة، فأنت في الواقع تعلن: «أنا أعرف أفضل من مؤلف المكتبة». ويسألك TypeScript: «هل أنت متأكد؟ أثبت ذلك».
\n\nلننظر إلى مثال تقليدي. لنفترض أن لدينا واجهة من مكتبة:
\n\n// node_modules/some-library/types.d.ts\nexport interface UserConfig {\n id: string | number;\n name: string;\n createdAt: Date | string;\n metadata: Record<string, unknown>;\n}\n\nتريد أن يكون createdAt من النوع Date فقط. الوراثة البسيطة لن تنجح - سيعطيك TypeScript خطأ إذا حاولت تضييق النوع. ولكن هناك طرق بديلة.
الطريقة 1: Declaration Merging (دمج التصريحات)
\n\nالطريقة الأكثر أناقة عندما تكون ممكنة. إذا كانت المكتبة تصدر واجهة (interface) وليس نوعًا (type)، يمكنك «تكميلها» في نطاق عام أو في وحدة نمطية.
شرط مهم: الواجهات التي تحمل نفس الاسم في نفس النطاق يتم دمجها تلقائيًا. لكن لا يمكن تجاوز خاصية موجودة - فقط إضافة خاصية جديدة.
\n\nلذلك، هذه الطريقة مناسبة عندما تحتاج إلى توسيع الواجهة، وليس تغيير حقل موجود. على سبيل المثال:
\n\n// global.d.ts\nexport interface UserConfig {\n customField: boolean; // ✅ إضافة حقل جديد\n}\n\n// أما هكذا - خطأ:\nexport interface UserConfig {\n createdAt: Date; // ❌ خطأ: تكرار المعرف\n}\n\nالخلاصة: Declaration Merging أداة ممتازة لإضافة الحقول، ولكنها عديمة الفائدة لتجاوز الحقول الموجودة.
\n\nالطريقة 2: استخدام Omit وواجهة مخصصة
\n\nهذه هي الرصاصة الفضية لـ 90% من الحالات. الفكرة بسيطة: «نقطع» الخاصية المشكلة باستخدام Omit وننشئ نوعًا جديدًا بالنوع المطلوب.
// الكود الخاص بك\ntype MyUserConfig = Omit<UserConfig, 'createdAt'> & {\n createdAt: Date; // ✅ الآن فقط Date\n};\n\n// الاستخدام\nconst config: MyUserConfig = {\n id: 42,\n name: 'Alice',\n createdAt: new Date('2024-01-01'), // يعمل\n metadata: { role: 'admin' }\n};\n\nماذا يحدث هنا؟ Omit<UserConfig, 'createdAt'> ينشئ نوعًا بدون حقل createdAt. ثم من خلال تقاطع الأنواع (&) نضيف هذا الحقل مرة أخرى، ولكن بالنوع المطلوب.
الإيجابيات: مثالي للتغييرات الدقيقة، لا يؤثر على الواجهة الأصلية.
\nالسلبيات: إذا كان هناك العديد من الخصائص، يصبح الكود ضخمًا.
الطريقة 3: التوسيع باستخدام Pick و Partial
\n\nأحيانًا تحتاج إلى تجاوز ليس خاصية واحدة، بل عدة خصائص. أو قد ترغب في جعل جميع الحقول اختيارية،