قصة كيفية قضاء يومين إعادة كتابة نفس الرمز عدة مرات.

دخول
في هذه المقالة ، سأحذف تفاصيل حول Hapi و Joi والتوجيه validate: { payload: ... }
، مما يعني ضمنيًا أنك تفهم بالفعل ما هو عليه ، وكذلك المصطلحات ، "واجهات" و "أنواع" وما شابه ذلك. . لن أخبركم سوى عن استراتيجية تعتمد على الدور وليس أكثرها نجاحًا ، ألا وهي تدريبي في هذه الأمور.
خلفية صغيرة
أنا الآن المطور الوحيد للجهة الخلفية (أي كتابة التعليمات البرمجية) في المشروع. الوظيفة ليست هي الجوهر ، ولكن الجوهر الرئيسي هو ملف تعريف طويل إلى حد ما مع البيانات الشخصية. تعتمد سرعة وجودة الشفرة على تجربتي الصغيرة في العمل بشكل مستقل في المشاريع من البداية ، وحتى أقل خبرة في العمل مع JS (الشهر الرابع فقط) ، وعلى طول الطريق ، بشكل منحرف للغاية ، أكتب في TypeScript (المشار إليها فيما يلي - TS). يتم ضغط التواريخ ، وضغط القوائم ، ووصول التعديلات باستمرار ، وقد ظهر لكتابة رمز منطق العمل أولاً ، ثم الواجهات في الأعلى. ومع ذلك ، فإن الواجب الفني قادر على اللحاق بالركب والتنصت على الغطاء ، وهو ما حدث لنا تقريبًا.
بعد 3 أشهر من العمل في المشروع ، اتفقت أخيرًا مع زملائي على التبديل إلى قاموس واحد بحيث تتم تسمية خصائص الكائن وكتابتها في كل مكان. تحت هذا العمل ، بالطبع ، تعهدت بكتابة واجهة وتعطلت معها لمدة يومين عمل.
المشكلة
سيكون ملف تعريف المستخدم البسيط مثالاً مجردة.
أولا خطوة الصفر لمطور جيد: وصف البيانات اكتب اختبارات- الخطوة الأولى:
اكتب الاختبارات وصف البيانات - و هكذا.
لنفترض أن الاختبارات قد تمت كتابتها بالفعل لهذا الرمز ، يبقى أن يصف البيانات:
interface IUser { name: string; age: number; phone: string | number; } const aleg: IUser = { name: 'Aleg', age: 45, phone: '79001231212' };
حسنًا ، كل شيء هنا واضح وبسيط للغاية. كل هذا الرمز ، كما نتذكر ، على الواجهة الخلفية ، أو بالأحرى ، في واجهة برمجة التطبيقات ، أي ، يتم إنشاء المستخدم استنادًا إلى البيانات التي جاءت عبر الشبكة. وبالتالي ، نحن بحاجة إلى التحقق من صحة البيانات الواردة ومساعدة Joi في هذا:
const joiUserValidator = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives([Joi.string(), Joi.number()]) };
الحل "على الجبهة" جاهز. ناقص واضح من هذا النهج هو أن المصادقة مطلقة تماما من واجهة. إذا تم تغيير / إضافة الحقول أثناء حياة التطبيق أو تغيير نوعها ، فسيكون من الضروري تتبع هذا التغيير يدويًا والإشارة إليه في المدقق. أعتقد أنه لن يكون هناك مطورين مسؤولين حتى يسقط شيء ما. بالإضافة إلى ذلك ، في مشروعنا ، يتكون الاستبيان من 50 حقلًا على ثلاثة مستويات من التعشيش ومن الصعب للغاية فهم ذلك ، حتى معرفة كل شيء عن ظهر قلب.
لا يمكننا ببساطة تحديد const joiUserValidator: IUser
، لأن Joi
يستخدم أنواع البيانات الخاصة به ، مما يولد أخطاء عند تجميع النوع Type 'NumberSchema' is not assignable to type 'number'
. ولكن يجب أن يكون هناك طريقة لأداء التحقق من الصحة على الواجهة؟

ربما لم أكن جوجل جوجل بشكل صحيح ، أو درست الإجابات بشكل سيئ ، ولكن كل القرارات جاءت extractTypes
أنواع extractTypes
من الدراجات الشرسة ، مثل هذا :
type ValidatedValueType<T extends joi.Schema> = T extends joi.StringSchema ? string : T extends joi.NumberSchema ? number : T extends joi.BooleanSchema ? boolean : T extends joi.ObjectSchema ? ValidatedObjectType<T> : never;
قرار
استخدم مكتبات الجهات الخارجية
لم لا. عندما سألت الناس عن مهمتي ، تلقيت في واحدة من الإجابات ، وبعد ذلك ، وهنا ، في التعليقات (بفضل keenondrums ) ، روابط لهذه المكتبات:
https://github.com/typestack/class-validator
https://github.com/typestack/class-transformer
ومع ذلك ، كان هناك اهتمام لمعرفة ذلك بنفسك ، لفهم عمل TS بشكل أفضل ، ولم يكن هناك شيء يحث على حل المشكلة في لحظات.
الحصول على جميع الخصائص
نظرًا لعدم وجود عمل سابق لدي في الإحصائيات ، اكتشف الرمز أعلاه أمريكا من حيث استخدام العوامل الثلاثية في الأنواع. لحسن الحظ ، لم يكن من الممكن تطبيقه في المشروع. لكنني وجدت دراجة أخرى مثيرة للاهتمام:
interface IUser { name: string; age: number; phone: string | number; } type UserKeys<T> = { [key in keyof T]; } const evan: UserKeys<IUser> = { name: 'Evan', age: 32, phone: 791234567890 }; const joiUser: UserKeys<IUser> = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives([Joi.string(), Joi.number()]) };
يتيح لك TypeScript
ظل ظروف صعبة وغامضة الحصول على ، على سبيل المثال ، مفاتيح من الواجهة ، كما لو كان كائن JS عاديًا ، ومع ذلك ، فقط في بنية type
ومن خلال key in keyof T
وفقط من خلال الوراثة. كنتيجة لنوع UserKeys
، يجب أن يكون لكل الكائنات التي تقوم بتنفيذ الواجهات نفس مجموعة الخصائص ، لكن أنواع القيم يمكن أن تكون عشوائية. يتضمن ذلك تلميحات في IDE ، ولكن لا يزال لا يعطي إشارة واضحة لأنواع القيم.
هنا حالة أخرى مثيرة للاهتمام لم أستطع استخدامها. ربما يمكنك أن تخبرني لماذا هذا ضروري (على الرغم من أنني أخمن جزئيًا ، لا يوجد مثال تطبيقي كافٍ):
interface IUser { name: string; age: number; phone: string | number; } interface IUserJoi { name: Joi.StringSchema, age: Joi.NumberSchema, phone: Joi.AlternativesSchema } type UserKeys<T> = { [key in keyof T]: T[key]; } const evan: UserKeys<IUser> = { name: 'Evan', age: 32, phone: 791234567890 }; const userJoiValidator: UserKeys<IUserJoi> = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives([Joi.string(), Joi.number()]) };
استخدام أنواع متغير
يمكنك تعيين الأنواع بشكل صريح ، واستخدام "OR" واستخراج الخصائص ، والحصول على رمز عمل محلي:
type TString = string | Joi.StringSchema; type TNumber = number | Joi.NumberSchema; type TStdAlter = TString | TNumber; type TAlter = TStdAlter | Joi.AlternativesSchema; export interface IUser { name: TString; age: TNumber; phone: TAlter; } type UserKeys<T> = { [key in keyof T]; } const olex: UserKeys<IUser> = { name: 'Olex', age: 67, phone: '79998887766' }; const joiUser: UserKeys<IUser> = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives([Joi.string(), Joi.number()]) };
تتجلى مشكلة هذه التعليمة البرمجية عندما نريد التقاط كائن صالح ، على سبيل المثال ، من قاعدة البيانات ، أي أن TS لا يعرف مقدمًا نوع البيانات التي ستكون - بسيطة أو Joi. قد يتسبب هذا في حدوث خطأ عند محاولة إجراء عمليات رياضية في حقل متوقع number
:
const someUser: IUser = getUserFromDB({ name: 'Aleg' }); const someWeirdMath = someUser.age % 10;
يأتي هذا الخطأ من Joi.NumberSchema
لأنه لا يمكن أن يكون العمر مجرد number
. ما قاتلوا من أجل وركض إلى.
الجمع بين حلين في واحد؟
في مكان ما في هذه المرحلة ، كان يوم العمل يقترب من نهايته المنطقية. أخذت نفسًا وشربت القهوة ومحو اللعنة. من الضروري قراءة هذه الإنترنت لديك بشكل أقل! لقد حان الوقت خذ بندقية و العقول poraskinut:
- يجب تكوين كائن مع أنواع قيمة واضحة ؛
- يمكنك استخدام الأدوية العامة لرمي الأنواع في واجهة واحدة ؛
- الأدوية الجنيسة تدعم الأنواع الافتراضية ؛
type
البناء قادر بوضوح على شيء آخر.
نكتب الواجهة العامة مع الأنواع الافتراضية:
interface IUser < TName = string, TAge = number, TAlt = string | number > { name: TName; age: TAge; phone: TAlt; }
بالنسبة لـ Joi ، يمكنك إنشاء واجهة ثانية ، وراثة الواجهة الرئيسية بهذه الطريقة:
interface IUserJoi extends IUser < Joi.StringSchema, Joi.NumberSchema, Joi.AlternativesSchema > {}
ليست جيدة بما يكفي ، لأن المطور التالي يمكنه توسيع IUserJoi
بقلب خفيف أو أسوأ من ذلك. خيار أكثر محدودية هو الحصول على سلوك مماثل:
type IUserJoi = IUser<Joi.StringSchema, Joi.NumberSchema, Joi.AlternativesSchema>;
نحن نحاول:
const aleg: IUser = { name: 'Aleg', age: 45, phone: '79001231212' }; const joiUser: IUserJoi = { name: Joi.string(), age: Joi.number(), phone: Joi.alternatives([Joi.string(), Joi.number()]) };
UPD:
في Joi.object
في Joi.object
اضطررت للقتال مع الخطأ TS2345
وكان الحل الأكثر بساطة هو as any
. أعتقد أن هذا ليس افتراضًا مهمًا ، لأن الكائن أعلاه لا يزال على الواجهة.
const joiUserInfo = { info: Joi.object(joiUser as any).required() };
إنه يجمع ، يبدو أنيقًا في مكان الاستخدام ، وفي حالة عدم وجود ظروف خاصة ، يقوم دائمًا بتعيين الأنواع الافتراضية! الجمال ...

... ما قضيت يومين عمل
تلخيص
ما هي الاستنتاجات التي يمكن استخلاصها من كل هذا:
- من الواضح أنني لم أتعلم كيفية العثور على إجابات للأسئلة. بالتأكيد ، مع طلب ناجح ، يكون هذا الحل (أو الأفضل) في أول 5 كيلو بايت من محرك البحث ؛
- إن التحول إلى التفكير الثابت من الديناميكية ليس بالأمر السهل ، وغالبًا ما أقوم بالتطويق على مثل هذا الاحتشاد ؛
- الأدوية الجنيسة رائعة. على Habr و stackoverflow ممتلئ
الدراجات حلول غير واضحة لبناء كتابة قوية ... خارج وقت التشغيل.
ما فزنا به:
- عند تغيير الواجهة ، تقع جميع الشفرات ، بما في ذلك المدقق ؛
- في المحرر ، ظهرت تلميحات حول أسماء الخصائص وأنواع قيم الكائنات لكتابة المدقق ؛
- عدم وجود مكتبات طرف ثالث غامضة لنفس الغرض ؛
- سيتم تطبيق قواعد Joi فقط عند الضرورة ، وفي حالات أخرى ، الأنواع الافتراضية ؛
- إذا أراد شخص ما تغيير نوع قيمة خاصية ما ، فحينها مع التنظيم الصحيح للرمز ، سوف ينتقل إلى المكان الذي يتم فيه جمع جميع الأنواع المرتبطة بهذه الخاصية ؛
- لقد تعلمنا بشكل جميل وببساطة إخفاء الأدوية الجنيسة خلف التجريد من
type
، حيث تم تفريغ الكود بصريًا من الإنشاءات الوحشية.
الأخلاقية: التجربة لا تقدر بثمن ؛ أما بالنسبة للباقي ، فهناك خريطة للعالم.
يمكنك أن ترى ، المس ، النتيجة النهائية:
https://repl.it/@Melodyn/Joi-by-interface
