TypeScript فعال: 62 طريقة لتحسين التعليمات البرمجية الخاصة بك

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



هيكل الكتاب



الكتاب عبارة عن مجموعة من المقالات القصيرة (القواعد). يتم تجميع القواعد في أقسام موضوعية (فصول) ، والتي يمكن الوصول إليها بشكل مستقل ، اعتمادًا على مسألة الاهتمام.



يحتوي كل عنوان قاعدة على تلميح ، لذا تحقق من جدول المحتويات. على سبيل المثال ، إذا كنت تكتب وثائق وتشك في ما إذا كنت تريد كتابة معلومات عن النوع ، فارجع إلى جدول المحتويات والقاعدة 30 ("لا تكرر معلومات الكتابة في الوثائق").

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



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



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



مقتطف. القاعدة 4. تعتاد على الكتابة الهيكلية



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



على سبيل المثال ، أنت تعمل مع مكتبة فيزياء ولديك نوع متجه ثنائي الأبعاد:



interface Vector2D {
   x: number;
   y: number;
}


تكتب دالة لحساب طولها:



function calculateLength(v: Vector2D) {
   return Math.sqrt(v.x * v.x + v.y * v.y);
}


وأدخل تعريف المتجه المسمى:



interface NamedVector {
   name: string;
   x: number;
   y: number;
}


ستعمل الدالة calculateLength مع NamedVector نظرًا لأنها تحتوي على خاصيتي x و y ، وهما رقم. يتفهم TypeScript هذا:



const v: NamedVector = { x: 3, y: 4, name: 'Zee' };
calculateLength(v); // ok,   5.


الشيء المثير للاهتمام هو أنك لم تعلن عن علاقة بين Vector2D و NamedVector. لم يكن عليك أيضًا كتابة تنفيذ حساب طول بديل لـ NamedVector. يحاكي نظام النوع TypeScript سلوك وقت تشغيل JavaScript (القاعدة 1) ، مما سمح لـ NamedVector باستدعاء calculateLength بناءً على هيكله الذي يمكن مقارنته بـ Vector2D. ومن هنا جاءت عبارة "الكتابة الهيكلية".



ولكن يمكن أن يؤدي أيضًا إلى مشاكل. لنفترض أنك أضفت نوع متجه ثلاثي الأبعاد:



interface Vector3D {
   x: number;
   y: number;
   z: number;
}


واكتب دالة لتطبيع المتجهات (اجعل طولها يساوي 1):



function normalize(v: Vector3D) {
   const length = calculateLength(v);
   return {
      x: v.x / length,
      y: v.y / length,
      z: v.z / length,
   };
}


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



> normalize({x: 3, y: 4, z: 5})
{ x: 0.6, y: 0.8, z: 1 }


ما الخطأ الذي حدث ولماذا لم يبلغ TypeScript عن خطأ؟



الخطأ هو أن حساب الطول يعمل مع المتجهات ثنائية الأبعاد ، بينما يعمل التطبيع مع الأبعاد الثلاثية. لذلك ، يتم تجاهل المكون z أثناء التطبيع.



قد يبدو من الغريب أن مدقق النوع لم يمسك بهذا. لماذا يُسمح باستدعاء حساب الطول على متجه ثلاثي الأبعاد ، على الرغم من أن نوعه يعمل مع متجهات ثنائية الأبعاد؟

ما نجح مع اسمه أدى إلى نتائج عكسية هنا. لن يؤدي استدعاء calculateLength على الكائن {x، y، z} إلى حدوث خطأ. لذلك ، لا تشكو وحدة فحص النوع ، مما يؤدي في النهاية إلى ظهور خطأ. إذا كنت ترغب في اكتشاف الخطأ في مثل هذه الحالة ، فراجع القاعدة 37.



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



يؤدي هذا أحيانًا إلى مفاجآت:



function calculateLengthL1(v: Vector3D) {
   let length = 0;
   for (const axis of Object.keys(v)) {
      const coord = v[axis];
                       // ~~~~~~~     "any",  
                       //  "string"    
                       //    "Vector3D"
      length += Math.abs(coord);
   }
   return length;
}


لماذا هذا خطأ؟ نظرًا لأن المحور هو أحد مفاتيح v من Vector3D ، فيجب أن يكون x أو y أو z. ووفقًا لإعلان Vector3D الأصلي ، فجميعها أرقام. لذلك ، ألا يجب أن يكون نوع التنسيق رقمًا أيضًا؟



هذا ليس خطأ خطأ. نحن نعلم أن Vector3D محدد بدقة وليس له خصائص أخرى. على الرغم من أنه يستطيع:



const vec3D = {x: 3, y: 4, z: 1, address: '123 Broadway'};
calculateLengthL1(vec3D); // ok,  NaN


نظرًا لأنه من المحتمل أن يكون لـ v أي خصائص ، يكون المحور من نوع السلسلة. لا يوجد سبب يدعو TypeScript إلى التفكير في [المحور] على أنه مجرد رقم. عند التكرار فوق الكائنات ، قد يكون من الصعب تحقيق الكتابة الصحيحة. سنعود إلى هذا الموضوع في القاعدة 54 ، لكن في الوقت الحالي لا نستخدم الحلقات:



function calculateLengthL1(v: Vector3D) {
   return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z);
}


يمكن أن تسبب الكتابة الهيكلية أيضًا مفاجآت في الفئات التي تتم مقارنتها بتخصيصات الممتلكات المحتملة:



class C {
   foo: string;
   constructor(foo: string) {
       this.foo = foo;
   }
}

const c = new C('instance of C');
const d: C = { foo: 'object literal' }; // ok!


لماذا يمكن تعيين d لـ C؟ لها خاصية foo وهي سلسلة. يحتوي أيضًا على مُنشئ (من Object.prototype) يمكن استدعاؤه باستخدام وسيطة (على الرغم من أنه يُطلق عليه عادةً بدونه). لذا فإن الهياكل هي نفسها. يمكن أن يؤدي هذا إلى مفاجآت إذا كان لديك منطق في مُنشئ C وكتبت دالة لاستدعاءها. هذا اختلاف كبير عن لغات مثل C ++ أو Java ، حيث تضمن إعلانات معلمة نوع C أنها تنتمي إلى C أو فئة فرعية منها.



تساعد الكتابة الهيكلية كثيرًا عند كتابة الاختبارات. لنفترض أن لديك وظيفة تنفذ استعلام قاعدة بيانات وتعالج النتيجة.



interface Author {
   first: string;
   last: string;
}
function getAuthors(database: PostgresDB): Author[] {
   const authorRows = database.runQuery(`SELECT FIRST, LAST FROM
                                 AUTHORS`);
   return authorRows.map(row => ({first: row[0], last: row[1]}));
}


لاختباره ، يمكنك إنشاء نموذج PostgresDB. ومع ذلك ، فإن الحل الأفضل هو استخدام الكتابة المنظمة وتحديد واجهة أضيق:



interface DB {
   runQuery: (sql: string) => any[];
}
function getAuthors(database: DB): Author[] {
   const authorRows = database.runQuery(`SELECT FIRST, LAST FROM
                                 AUTHORS`);
   return authorRows.map(row => ({first: row[0], last: row[1]}));
}


لا يزال بإمكانك تمرير postgresDB إلى دالة getAuthors في الإخراج ، نظرًا لأنه يحتوي على طريقة runQuery. لا تلزم الكتابة الهيكلية PostgresDB بالإبلاغ عن تنفيذ قاعدة بيانات. سوف تيب سكريبت اكتشاف ذلك من أجلك.



عند كتابة الاختبارات ، يمكنك أيضًا اجتياز كائن أبسط:



test('getAuthors', () => {
   const authors = getAuthors({
      runQuery(sql: string) {
         return [['Toni', 'Morrison'], ['Maya', 'Angelou']];
      }
   });
   expect(authors).toEqual([
      {first: 'Toni', last: 'Morrison'},
      {first: 'Maya', last: 'Angelou'}
   ]);
});


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



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







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



ضع في اعتبارك أن الفئات تخضع أيضًا لقواعد الكتابة الهيكلية. لذلك ، قد ينتهي بك الأمر بعينة فئة مختلفة عن المتوقع.



استخدم الكتابة المنظمة لتسهيل اختبار العناصر.


القاعدة 5. تقييد استخدام أي أنواع



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



   let age: number;
   age = '12';
// ~~~  '"12"'       'number'.
   age = '12' as any; // ok


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



تقليل أمان الكود



في المثال أعلاه ، وفقًا للنوع المعلن ، يكون العمر هو الرقم. لكن أي سلسلة مسموح بها لتخصيصها لها. سيفترض المدقق أن هذا رقم (وهو ما أعلنته) ، مما سيؤدي إلى حدوث ارتباك.



age += 1; // ok.    age "121".


يسمح لك بانتهاك الشروط



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



function calculateAge(birthDate: Date): number {
   // ...
}

let birthDate: any = '1990-01-19';
calculateAge(birthDate); // ok


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



يلغي دعم خدمة اللغة



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



صورة


ومع ذلك ، من خلال تخصيص أي نوع للأحرف ، عليك أن تفعل كل شيء بنفسك (الشكل 1.4).



صورة


وإعادة التسمية أيضًا. إذا كان لديك نوع الشخص والوظائف لتنسيق الاسم:



interface Person {
   first: string;
   last: string;
}

const formatName = (p: Person) => `${p.first} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;


ثم يمكنك التمييز أولاً في المحرر وتحديد إعادة تسمية الرمز لإعادة تسميته إلى الاسم الأول (الشكل 1.5 والشكل 1.6).



سيؤدي هذا إلى تغيير وظيفة formatName ، ولكن ليس في حالة أي:



interface Person {
   first: string;
   last: string;
}

const formatName = (p: Person) => `${p.firstName} ${p.last}`;
const formatNameAny = (p: any) => `${p.first} ${p.last}`;




صورة




»يمكن العثور على مزيد من التفاصيل حول الكتاب على الموقع الإلكتروني لدار النشر

» جدول المحتويات

» مقتطفات للساكنين



خصم 25٪ على القسيمة - TypeScript



عند الدفع مقابل النسخة الورقية من الكتاب ، يتم إرسال كتاب إلكتروني عن طريق البريد الإلكتروني.



All Articles