جعل TypeScript أكثر صرامة. تقرير ياندكس

كيف تجعل TypeScript رفيقًا صارمًا ولكن عادلًا يحميك من الأخطاء السيئة ويمنحك المزيد من الثقة في التعليمات البرمجية الخاصة بك؟ أليكسي فيسيلوفسكي فيسيلوفسكياعتبر العديد من ميزات تكوين TS التي تغض الطرف عن الحريات التي لا تُغتفر. يحدد التقرير الأشياء التي من الأفضل تجنبها وتلك التي تحتاج إلى توخي الحذر الشديد معها. ستتعرف على مكتبة io-ts الرائعة - فهي تتيح لك اكتشاف البيانات بسهولة وحتى منع إدخال الشفرة التي يمكن أن تسبب أخطاء في الأماكن المكتوبة بشكل مثالي.



- مرحبًا بالجميع ، اسمي ليشا ، أنا مطور الواجهة. لنبدأ. سأخبرك قليلاً عن نفسي وعن المشروع الذي أعمل فيه. يتعلم Flow اللغة الإنجليزية من Yandex.Practicum. تم الإفراج عنهم في أبريل من هذا العام. تمت كتابة الواجهة مباشرة بلغة TypeScript ، قبل ذلك لم يكن هناك رمز.







قليلا عن تجربتي. في عام بعيد ، بدأت البرمجة. في عام 2013 ، بدأ العمل.







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



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







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



هناك قلق أقل بشأن الأنواع. عندما نقوم بإنشاء وظائف جديدة ، نقوم على الفور بتعيين الأنواع التي تعمل بها الوظائف ، ولا يقلقنا القلق بشأن تلقي بيانات مختلفة.



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







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







وهذا هو تضمين صارم. لم يكن موجودًا ، لكن كان يلزم تشغيله لتحسين التحقق.



TypeScript: صارم



ما هو صارم؟ مما تتكون؟







هذه مجموعة من الأعلام يمكن تشغيلها بشكل فردي ، لكن في رأيي كلها مفيدة للغاية. noImplicitAny - قبل تمكين هذه العلامة ، يمكننا أن نعلن ، على سبيل المثال ، الوظائف التي ستكون معلماتها ضمنية ، مثل أي. إذا قمنا بتمكين هذه العلامة ، فيجب علينا إضافة الكتابة في الأماكن التي يتعذر فيها على TypeScript حساب النوع من السياق.



هذا ، في الحالة الثانية ، يجب أن نضيف الكتابة ، لأنه لا يوجد سياق على هذا النحو. في الحالة الثالثة ، حيث لدينا خريطة ، لا يمكننا إضافة كتابة لـ a ، لأنه يتضح من السياق أنه سيكون هناك نوع رقم.







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







دائما صارمة. يضيف "استخدام صارم" لكل ملف. لكنها تؤثر على كيفية تنفيذ JavaScript لشفرتنا. (...)







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







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







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



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







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



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







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



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



من خلال هذه الإجراءات ، قمنا حتى الآن بتقليل عدد الأخطاء من 400 إلى 200. ونتطلع إلى الانتقال إلى المرحلة الثالثة - إزالة webpackTsConfig واستخدام tsconfig عند البناء ، ولكن مع تفعيل صارم.



TypeScript:



يمكنك التحدث قليلاً عن التفاصيل الدقيقة لـ TypeScript التي لم يتم تغطيتها بواسطة صارم ، لكن من الصعب إضفاء الطابع الرسمي عليها بشكل صحيح.







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



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



لنفترض أن لدينا مثل هذا الموقف.







يوجد عنصر ، يمكن أن يكون من نوع الارتباط أو الامتداد. من خلال رأسنا نفهم أن الامتداد هو نص فقط ، والرابط هو نص ورابط.



(صورة)



لكننا نسينا إخبار لغة TypeScript ، لذلك في وظيفة getItemHtml ، تنشأ حالة أنه في حالة الارتباط ، يجب أن نقول: href ليس اختياريًا ، سيكون بالتأكيد كذلك. هذا أيضًا مكان محتمل للخطأ. كيف تصلحها؟







الخيار الأول هو تصحيح الكتابة ، أي للإشارة صراحةً إلى TypeScript إلى أن href مطلوب للارتباط ، واختياري لـ span.







ولن تكون هناك حاجة لعلامة التعجب هنا.







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







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



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



مثل





بالإضافة إلى ذلك. هناك أيضا عامل. ماذا يسمح لك ان تفعل؟







يسمح لك أن تقول - أعرف أفضل ، هناك كذا وكذا - وأيضًا تقود نفسك إلى الخطأ.



المصفوفات



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







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



شاء



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







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



أي



الآن الشيء الواضح هو أي.







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



مرة أخرى ، من الأفضل عدم السماح بهذا صراحة باستخدام ESLint. ولكن هناك حالات عندما تظهر من تلقاء نفسها.







على سبيل المثال ، في هذه الحالة ، ينتج عن JSON.parse هذا النوع فقط أي. ماذا يمكن ان يفعل؟







يمكنك فقط أن تقول: لا أصدقك ، دعنا نقول أفضل إنني لا أعرف ما هو ، وسأعيش معه. كيف تتعايش معها؟ هذا مثال افتراضي.







يوجد مستخدم ، المستخدم لديه اسم مطلوب وبريد إلكتروني اختياري.







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







إذا لم يكن كائنًا أو كان فارغًا ، فقم بإلقاء خطأ.







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







نبدأ في تكوين المستخدم ، حيث تم بالفعل جمع جميع الحقول المطلوبة.







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







ويتطلب الكثير من الشيكات



نحتاج إلى الكثير من التحقق لأن طلب JSON النموذجي يبدو هكذا.







بدون مزيد من اللغط ، هذا مجرد جلب و json (). يظهر التحويل من أي إلى SomeRequestResponse في المقابل. هذا يحتاج أيضا إلى محاربته. يمكنك القيام بذلك بالطريقة السابقة ، أو يمكنك القيام بذلك بطريقة مختلفة قليلاً.



io-ts



إنه نفسه تحت الغطاء: نستخدم مكتبة خاصة لفحص النوع. في هذه الحالة ، هو io-ts. إليك مثال بسيط عن كيفية التعامل معها.







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







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



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



عرض الأخطاء







حول عرض الأخطاء. يمكنك تحسينه قليلا. سنستخدم مراسلي io-ts لهذا الغرض. هذه مكتبة كتبها نفس المؤلف مثل io-ts. يسمح بعرض الخطأ بشكل جميل. ماذا تفعل؟ قمنا بتغيير الرمز هنا حيث توجد البطة. يأخذ النتيجة ويعيد مجموعة من السلاسل. نحن فقط ننضم إليه في سطر واحد ونعرضه. ماذا نحصل نتيجة لذلك؟







لنفترض أننا نمرر قيمة خالية إلى سلسلة JSON.







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







بعد ذلك ، دعنا نحاول تمرير مصفوفة فارغة هناك. سيكون هو نفسه.







سيقول لنا ببساطة: لقد توقعت أيضًا كائنًا ، لكنني تلقيت مصفوفة فارغة.







لذلك ، ما زلنا نرى ما سيحدث إذا بدأنا في إرسال بيانات غير صحيحة. على سبيل المثال ، لنمرر كائنًا فارغًا.







الآن سيعطي خطأ واحدًا حول حقيقة أنه ليس لدينا حقل الاسم المطلوب. لقد توقع أن يكون حقل الاسم من نوع السلسلة ، لكنه ينتهي بـ غير معرف. من السهل أيضًا فهم ما حدث من هذا الخطأ.







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







لكنه هنا يكتب لنا بوضوح المعنى الذي نقلناه.







ماذا يمكن أن تفعل io-ts أيضًا؟ يسمح لك بالحصول على نوع TypeScript. أي نضيف هذا الخط. عن طريق إضافة typeof ، وكذلك typeof ، نحصل على نوع TypeScript الذي يمكننا استخدامه بشكل أكبر في التطبيق. ملائم.







ماذا يمكن أن تفعل هذه المكتبة أيضًا؟ أنواع التحويل. لنفترض أننا نتقدم بطلب إلى الخادم. يرسل الخادم التواريخ بتنسيق وقت يونيكس. وهناك مكتبة خاصة ، مرة أخرى من مبتكر مكتبة io-ts: io-ts-types. هناك تحويلات تمت كتابتها في الأصل ، وأدوات لجعل تلك التحولات أسهل في الكتابة. نضيف حقل تاريخ: يأتي من الخادم كرقم ، وينتهي بنا الأمر باستلامه ككائن تاريخ.



دعنا نصف النوع



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







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



لنبسط مهمتنا قليلاً. سنكتب فقط النوع الذي يتحقق. في هذه الحالة ، لنكتشف ما تعنيه هذه الحقول. الاسم - اكتب الاسم.







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



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



هناك وظيفتان أخريان: is and encode. يتم استخدامها لتحويلها عكسيًا ، لكن دعونا لا نلمسها الآن.







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



في الممارسة



ما هو عملي؟ لماذا قررنا التحقق من البيانات التي تأتي من الخادم على الإطلاق؟







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



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



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







ماذا وجدنا؟ لقد بدأنا بالفعل في استخدامه قليلاً. وجدت أن الخادم يعطينا كائنًا فارغًا بدلاً من مصفوفة فارغة. لقد بحثت للتو في الكود - المكتوب لإرجاع كائن فارغ.



علاوة على ذلك - عدم وجود بعض المجالات. كنا نظن أنها إلزامية ، لكن تبين أنها اختيارية.



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







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



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







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



شكرا للاهتمام.



All Articles