عندما رأيت الكلمة لأول مرة أبدًا ، فكرت في عدم ظهور النوع في TypeScript. مع مرور الوقت ، غرقت في عمق ts ، بدأت أفهم مدى قوة هذه الكلمة. وتولد هذه القوة من حالات الاستخدام في العالم الحقيقي التي أعتزم مشاركتها مع القارئ. من يهتم ، مرحبا بكم في القط.
ما ليس ابدا
إذا غرقنا في التاريخ ، فسنرى أن النوع لم يظهر أبدًا في فجر الإصدار 2.0 من برنامج TypeScript ، مع
وصف بسيط إلى حد ما للغرض منه. إذا قمت باختيار إصدار مطوري ts لفترة وجيزة وحرية ، فإن النوع لن يكون أبدًا نوعًا بدائيًا يمثل علامة للقيم التي لن تحدث أبدًا. أو ، علامة للوظائف التي لن تُرجع القيم أبدًا ، إما بسبب الحلقة الخاصة بها ، على سبيل المثال ، حلقة لا نهائية ، أو بسبب انقطاعها. ولإظهار جوهر ما قيل بوضوح ، أقترح رؤية مثال أدناه:
function error(message: string): never { throw new Error(message); } function infiniteLoop(): never { while (true) { } } function infiniteRec(): never { return infiniteRec(); }
ربما بسبب هذه الأمثلة ، حصلت على الانطباع الأول بأن النوع مطلوب للوضوح.
اكتب النظام
الآن أستطيع أن أقول أن الحيوانات الغنية بالنوع النظام في TypeScript كذلك لا يعود أبدا. ودعماً لكلماتي ، سأقدم العديد من أنواع المكتبات من lib.es5.d.ts
type Exclude<T, U> = T extends U ? never : T; type Extract<T, U> = T extends U ? T : never; type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; type NonNullable<T> = T extends null | undefined ? never : T; type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
من بين أنواعي التي لا تستخدمها أبدًا ، سأعطي المفضل لدي - GetNames ، تناظرية محسنة لـ keyof:
type GetNames<FromType, KeepType = any, Include = true> = { [K in keyof FromType]: FromType[K] extends KeepType ? Include extends true ? K : never : Include extends true ? never : K }[keyof FromType];
مراقبة التغيرات المستقبلية
عندما لا تكون حياة المشروع سريعة الزوال ، يأخذ التطوير طابعًا خاصًا. أرغب في التحكم في النقاط الرئيسية في التعليمات البرمجية ، للحصول على ضمانات بأنك أنت أو أعضاء فريقك لن تنسوا إصلاح جزء أساسي من الشفرة عندما يستغرق الأمر وقتًا. تظهر مقاطع التعليمات البرمجية هذه بشكل خاص عند وجود حالة أو تعداد لشيء ما. على سبيل المثال ، سرد مجموعة من الإجراءات على كيان. أقترح النظر في مثال لفهم سياق ما يحدث.
يتم تبسيط الرمز أعلاه قدر الإمكان فقط من أجل التركيز على نقطة مهمة - يتم تعريف نوع AdminAction في مشروع آخر ، وحتى أنه من غير الممكن أن يرافقه فريقك. نظرًا لأن المشروع سيعيش لفترة طويلة ، فمن الضروري حماية ActionEngine من التغييرات في نوع AdminAction دون علمك. يقدم برنامج TypeScript العديد من الوصفات لحل هذه المشكلة ، أحدها استخدام النوع أبدا. للقيام بذلك ، نحتاج إلى تعريف NeverError واستخدامه في طريقة doAction.
class NeverError extends Error {
أضف الآن قيمة "BLOCK" جديدة إلى AdminAction واحصل على خطأ في وقت الترجمة: وسيطة النوع "" BLOCK "" غير قابلة للتخصيص لمعلمة type 'never'.ts (2345).
من حيث المبدأ ، حققنا هذا. تجدر الإشارة إلى نقطة مثيرة للاهتمام وهي أن بناء التبديل يحمينا من تغيير عناصر AdminAction أو إزالتها من المجموعة. من الممارسة ، أستطيع أن أقول أنه يعمل حقًا كما هو متوقع.
إذا كنت لا ترغب في تقديم فئة NeverError ، فيمكنك التحكم في الكود عن طريق التصريح عن متغير النوع أبدًا. مثل هذا:
type AdminAction = "CREATE" | "ACTIVATE" | "BLOCK"; class ActionEngine { doAction(action: AdminAction) { switch (action) { case "CREATE":
حد السياق: هذا + أبداً
الخدعة التالية غالباً ما تنقذني من الأخطاء السخيفة وسط التعب أو الإهمال. في المثال أدناه ، لن أقدم تقييماً لجودة النهج المختار. معنا ، الأمر الواقع ، يحدث هذا. افترض أنك تستخدم أسلوبًا في فصل دراسي لا يملك حق الوصول إلى حقول الفصل الدراسي. نعم ، يبدو الأمر مخيفًا - كل هذا رمز حكومي.
@SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook() {
في حالة أوسع ، يمكن أن تكون وظائف رد الاتصال أو السهم ، والتي لها سياقات التنفيذ الخاصة بها. والمهمة هي: كيف تحمي نفسك من أخطاء وقت التشغيل؟ لهذا ، TypeScript لديه القدرة على تحديد
هذا السياق.
@SomeDecorator({...}) class SomeUiPanel { @Inject private someService: SomeService; public beforeAccessHook(this: never) {
في الإنصاف ، سأقول أنه لا يستحق أبدًا. بدلاً من ذلك ، يمكنك استخدام باطل و {}. ولكن هذا لن يحدث أبدًا عندما تقرأ الشفرة التي تجذب الانتباه.
توقعات
الثوابت
امتلاك فكرة مؤكدة عن عدم الظهور أبدًا ، أعتقد أن الشفرة التالية يجب أن تعمل:
type Maybe<T> = T | void; function invariant<Cond extends boolean>(condition: Cond, message: string): Cond extends true ? void : never { if (condition) { return; } throw new Error(message); } function f(x: Maybe<number>, c: number) { if (c > 0) { invariant(typeof x === "number", "When c is positive, x should be number"); (x + 1);
لكن للأسف. يؤدي التعبير (x + 1) إلى حدوث خطأ: لا يمكن تطبيق عامل التشغيل "+" على النوعين "ربما" و "1". المثال نفسه أنا تجسست في المقالة
نقل 30،000 سطر من التعليمات البرمجية من Flow إلى TypeScript.ملزمة مرنة
اعتقدت أنه بمساعدة من لا أستطيع أبداً التحكم في معلمات الوظيفة الإلزامية ، وفي ظل ظروف معينة ، قم بتعطيل تلك غير الضرورية. لكن لا ، هذا لن ينجح:
function variants<Type extends number | string>(x: Type, c: Type extends number ? number : never): number { if (typeof x === "number") { return x + c; } return +x; } const three = variants(1, 2);
يتم حل المشكلة المذكورة أعلاه
بطريقة مختلفة .
تحقق أكثر صرامة
أردت أن لا يفوت المترجم ts شيئًا من هذا القبيل يتعارض مع المنطق السليم.
variants(<never> {}, <never> {});
استنتاج
في النهاية ، أود أن أقدم مهمة صغيرة ، من سلسلة من الغرائب الغريبة. أي خط هو الخطأ؟
class never<never> { never: never; } const whats = new never<string>(); whats.never = "";
خيارفي الأخير: النوع "" "" غير قابل للتعيين للكتابة "أبدًا". (2322)
هذا كل ما أردت أن أخبر عنه أبداً. شكرا لكم جميعا على اهتمامكم ونراكم قريبا.