
في الآونة الأخيرة ، عند دراسة أسباب خلل مشروع منزلي ، لاحظت مرة أخرى وجود خطأ يتكرر غالبًا بسبب التعب. يكمن جوهر الخطأ في أنه بوجود عدة معرّفات في مقطع تعليمات برمجية واحد ، عند استدعاء دالة ، أقوم بتمرير معرف كائن من نوع آخر. في هذه المقالة سأتحدث عن كيفية حل هذه المشكلة باستخدام TypeScript.
قليلا من الناحية النظرية
يستند TypeScript إلى الكتابة الهيكلية ، والتي تتلاءم بشكل جيد مع أيديولوجية بطة JavaScript. لقد كتب عدد كاف من المقالات حول هذا الموضوع. لن أكررها ، وسأحدد الاختلاف الرئيسي فقط عن الكتابة الاسمية ، والتي هي أكثر شيوعًا في اللغات الأخرى. دعونا دراسة مثال صغير.
class Car { id: number; numberOfWheels: number; move (x: number, y: number) {
لماذا تتصرف TypeScript بهذه الطريقة؟ هذا مجرد مظهر من مظاهر الكتابة الهيكلية. بخلاف الاسمية ، التي تراقب أسماء الأنواع ، فإن الكتابة الهيكلية تتخذ قرارًا بشأن توافق الأنواع بناءً على محتوياتها. تحتوي فئة Car على جميع الخصائص والأساليب الخاصة بفئة Boat ، بحيث يمكن استخدام Car كقارب. العكس ليس صحيحًا لأن Boat ليس به خاصية numberOfWheels.
معرفات الكتابة
بادئ ذي بدء ، سنقوم بتعيين أنواع لمعرفات
type CarId: number; type BoatId: number;
وأعد كتابة الفصول باستخدام هذه الأنواع.
class Car { id: CarId; numberOfWheels: number; move (x: number, y: number) {
ستلاحظ أن الموقف لم يتغير كثيرًا ، لأننا ما زلنا لا نتحكم في المكان الذي حصلنا عليه من المعرف ، وسوف تكون على حق. ولكن هذا المثال يعطي بالفعل بعض المزايا.
أثناء تطوير البرنامج ، قد يتغير نوع المعرف فجأة. لذلك ، على سبيل المثال ، يمكن استبدال رقم سيارة معين ، فريد للمشروع ، برقم VIN سلسلة. بدون تحديد نوع المعرف ، سيتعين عليك استبدال الرقم بالسلسلة في جميع الأماكن التي يحدث فيها ذلك. مع مهمة الكتابة ، يجب إجراء التغيير في مكان واحد فقط حيث يتم تحديد النوع نفسه.
عند استدعاء الوظائف ، نحصل على تلميحات من محرر الكود الخاص بنا ، ما هي معرفات النوع التي يجب أن تكون. افترض أن لدينا الوظائف التالية المعلنة:
function getCarById(id: CarId): Car {
بعد ذلك ، سوف نحصل على تلميح من المحرر بأنه يجب علينا أن ننقل ليس فقط رقمًا ، ولكن أيضًا CarId أو BoatId.
محاكاة صارمة الكتابة
لا يوجد كتابة اسمية في TypeScript ، ولكن يمكننا محاكاة سلوكها ، مما يجعل أي نوع فريدًا. للقيام بذلك ، قم بإضافة خاصية فريدة إلى النوع. يشار إلى هذه الخدعة في مقالات باللغة الإنجليزية تحت عنوان العلامة التجارية ، وهنا ما يبدو:
type BoatId = number & { _type: 'BoatId'}; type CarId = number & { _type: 'CarId'};
بعد الإشارة إلى أن أنواعنا يجب أن تكون رقمًا وكائنًا مع خاصية ذات قيمة فريدة ، فقد جعلنا أنواعنا غير متوافقة في فهم الكتابة الهيكلية. دعونا نرى كيف يعمل.
let carId: CarId; let boatId: BoatId; let car: Car; let boat: Boat; car = getCarById(carId);
كل شيء يبدو جيدا باستثناء الأسطر الأربعة الأخيرة. لإنشاء معرفات ، تحتاج إلى وظيفة مساعد:
function makeCarIdFromVin(id: number): CarId { return vin as any; }
عيب هذه الطريقة هو أن هذه الوظيفة ستبقى في وقت التشغيل.
جعل الكتابة قوية أقل قليلا صارمة
في المثال الأخير ، اضطررت إلى استخدام وظيفة إضافية لإنشاء المعرف. يمكنك التخلص منه باستخدام تعريف واجهة Flavor:
interface Flavoring<FlavorT> { _type?: FlavorT; } export type Flavor<T, FlavorT> = T & Flavoring<FlavorT>;
يمكنك الآن تعيين أنواع للمعرفات على النحو التالي:
type CarId = Flavor<number, “CarId”> type BoatId = Flavor<number, “BoatId”>
نظرًا لأن خاصية _type اختيارية ، يمكنك استخدام تحويل ضمني:
let boatId: BoatId = 5; // OK let carId: CarId = 3; // OK
وما زلنا لا نستطيع خلط المعرفات:
let carId: CarId = boatId; // ERROR
أي خيار للاختيار
كلا الخيارين لهما الحق في الوجود. تتمتع العلامة التجارية بميزة حماية المتغير من التعيين المباشر. يكون هذا مفيدًا إذا قام المتغير بتخزين السلسلة بتنسيق ما ، مثل مسار الملف المطلق أو التاريخ أو عنوان IP. يمكن لوظيفة المساعد التي تتعامل مع تحويل النوع في هذه الحالة أيضًا التحقق من بيانات الإدخال ومعالجتها. في حالات أخرى ، يكون استخدام Flavor أكثر ملاءمة.
مصادر
- نقطة انطلاق stackoverflow.com
- تفسير حر لهذه المادة