الكتابة الاسمية في TypeScript أو كيفية حماية الواجهة من معرفات غريبة


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


قليلا من الناحية النظرية


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


class Car { id: number; numberOfWheels: number; move (x: number, y: number) { //   } } class Boat { id: number; move (x: number, y: number) { //   } } let car: Car = new Boat(); //  TypeScript   let boat: Boat = new Car(); //        

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


معرفات الكتابة


بادئ ذي بدء ، سنقوم بتعيين أنواع لمعرفات


 type CarId: number; type BoatId: number; 

وأعد كتابة الفصول باستخدام هذه الأنواع.


 class Car { id: CarId; numberOfWheels: number; move (x: number, y: number) { //   } } class Boat { id: BoatId; move (x: number, y: number) { //   } } 

ستلاحظ أن الموقف لم يتغير كثيرًا ، لأننا ما زلنا لا نتحكم في المكان الذي حصلنا عليه من المعرف ، وسوف تكون على حق. ولكن هذا المثال يعطي بالفعل بعض المزايا.


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


  2. عند استدعاء الوظائف ، نحصل على تلميحات من محرر الكود الخاص بنا ، ما هي معرفات النوع التي يجب أن تكون. افترض أن لدينا الوظائف التالية المعلنة:


     function getCarById(id: CarId): Car { // ... } function getBoatById(id: BoatId): Boat { // ... } 

    بعد ذلك ، سوف نحصل على تلميح من المحرر بأنه يجب علينا أن ننقل ليس فقط رقمًا ، ولكن أيضًا 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); // OK car = getCarById(boatId); // ERROR boat = getBoatById(boatId); // OK boat = getBoatById(carId); // ERROR carId = 1; // ERROR boatId = 2; // ERROR car = getCarById(3); // ERROR boat = getBoatById(4); // ERROR 

كل شيء يبدو جيدا باستثناء الأسطر الأربعة الأخيرة. لإنشاء معرفات ، تحتاج إلى وظيفة مساعد:


 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 أكثر ملاءمة.


مصادر


  1. نقطة انطلاق stackoverflow.com
  2. تفسير حر لهذه المادة

Source: https://habr.com/ru/post/ar446768/


All Articles