افعل ولا تفعل في TypeScript

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث


الأنواع العامة

Number‎، و‎String‎، و‎Boolean‎، و‎Object

لا تستعمل أبدًا الأنواع ‎Number‎، و‎String‎، و‎Boolean‎، و‎Object‎. هذه الأنواع تشير إلى الكائنات غير الأوليّة المحاطة (non-primitive boxed objects) التي لا تُستعمَل في أغلب الأحيان بطريقة صحيحة في شيفرة JavaScript:

/* خطأ */
function reverse(s: String): String;

استعمل بدلًا منها الأنواع ‎number‎، و‎string‎، و‎boolean‎:

/* صحيح */
function reverse(s: string): string;

استعمل النوع غير الأولي ‎object‎ (الذي أُضِيفَ في TypeScript 2.2) عوضًا عن النوع ‎Object‎.

الأنواع المعممة (Generics)

لا تُعرِّف أبدًا نوعًا معمّمًا لا يستخدم معامل أنواعه (type parameter). انظر صفحة الأسئلة الشائعة للاستزادة.

أنواع دوال رد النداء (Callback Types)

الأنواع المعادة (Return Types) لدوال رد النداء

لا تستعمل النوع ‎any‎ كنوعٍ معاد لدوال رد النداء التي ستُتجاهل قيمتها المعادة:

/* خطأ */
function fn(x: () => any) {
    x();
}

استعمل عوضًا عنه النوعَ ‎void‎ كنوع معاد لدوال رد النداء التي تُتَجاهل قيمتها المعادة:

/* صحيح */
function fn(x: () => void) {
    x();
}

السبب: استعمال ‎void‎ أَأْمَنُ لأنّه يمنعك من استخدام القيمة المعادة التي تعيدها ‎x‎ دون قصدٍ بطريقة غير متحقَّق منها:

function fn(x: () => void) {
    var k = x(); // خطأ
// سيُطلَق خطأ، لكن لو كان النوع المعاد النوعَ
// 'any'
// لما أُخْبِرنَا بالخطأ
    k.doSomething();
}

المعاملات الاختيارية في دوال رد النداء

لا تستعمل المعاملات الاختيارية في دوال رد النداء إلا إذا كنت تعني ذلك حقًّا (وليس أن تقصد من ذلك إمكانية عدم تمرير قيمة للمعامل):

/* خطأ */
interface Fetcher {
    getObject(done: (data: any, elapsedTime?: number) => void): void;
}

يحمل هذا معنًى خاصًّا: قد تُستدعَى دالة رد النداء ‎done‎ بمعامل واحدٍ أو قد تُستَدعى بمعاملين. أغلب الظن أنّ من كتب المثال أعلاه يقصد أن دالة رد النداء قد لا تكثرت للمعامل ‎elapsedTime‎، لكن لا حاجة لجعل المعامل اختياريًّا لهذا الغرض، إذ أنّ توفير عدد معاملات لدالة رد نداء أقلّ ممّا تقبله أمرٌ مسموح به دائمًا.

اكتب معاملات دوال رد النداء على أنّها غير اختياريّة:

/* صحيح */
interface Fetcher {
    getObject(done: (data: any, elapsedTime: number) => void): void;
}

الأحمال الزائدة (Overloads) ودوال رد النداء

لا تكتب أحمالًا زائدة متعددة تختلف فقط في جزء دالة رد النداء:

/* خطأ */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;

اكتب حملا زائدا واحدًا باستخدام نوع دالة رد النداء الأكثر تفصيلا:

/* صحيح */
declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;

السبب: يُسمَح دائمًا لدالة رد نداءٍ بتجاهل معاملٍ أو عدة معاملات، لذا لا حاجة لاستعمال الحمل الزائد الأقصر. وضعُ دالة رد نداء أقصر في المقدمة يسمح للدوال التي لا تُكتَب أنواعها بشكل سليم بأن تُمرَّر لأنها توافق الحمل الزائد الأول.

أحمال الدوال الزائدة (Function Overloads)

الترتيب

لا تضع الأحمال الزائدة الأكثر شمولًا قبل الأحمال الزائدة الأكثر تخصّصًا:

/* خطأ */
declare function fn(x: any): any;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;

var myElem: HTMLDivElement;
var x = fn(myElem); // x: any. هذا ليس المقصود

رتّب الأحمال الزائدة عبر وضع التواقيع العامة بعد التواقيع المتخصّصة:

/* صحيح */
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: any): any;

var myElem: HTMLDivElement;
var x = fn(myElem); // x: string. هذا هو المبتغى

السبب: تختار TypeScript أول حملٍ زائدٍ موافقٍ عند تقرير (resolving) استدعاءات الدوال. عندما يكون حمل زائد سابقٌ أعمَّ من الذي يليه، فالذي يليه يُخفى ولا يمكن استدعاءه (لأنّه يغطّى من طرف الحمل الزائد الأعمّ).

استعمل المعاملات الاختيارية

لا تكتب عدّة أحمال زائدة تختلف فقط في المعاملات الموجودة في آخر القائمة:

/* خطأ */
interface Example {
    diff(one: string): number;
    diff(one: string, two: string): number;
    diff(one: string, two: string, three: boolean): number;
}

استعمل المعاملات الاختيارية كلما أمكن ذلك:

/* صحيح */
interface Example {
    diff(one: string, two?: string, three?: boolean): number;
}

لاحظ أنّ هذا التجميع مطلوب فقط عندما يكون لجميع الأحمال الزائدة نفس النوع المعاد.

هذا مهم لسببين:

السبب الأول هو أنّ TypeScript تقرِّر توافقيّة التواقيع (signature compatibility) عبر التحقق ممّا إذا كان أي توقيعٍ من تواقيع الهدف قابلا لاستدعائه بقيم المعاملات الممررة للمصدر، والمعاملات الدخيلة (extraneous arguments) مسموح بها. هذه الشيفرة على سبيل المثال تحتوي على علّة فقط إذا كان التوقيع مكتوبًا بشكل صحيح باستخدام المعاملات الاختيارية:

function fn(x: (a: string, b: number, c: number) => void) { }
var x: Example;
// عند الكتابة بالأحمال الزائدة، فسيُستخدَم الحمل الزائد الأول ولن نحصل على خطأ
// عند الكتابة بالمعاملات الاختيارية، فسنحصل على خطأ (وهذا هو المقصود)
fn(x.diff);

السبب الثاني يكون في حالة اعتماد مُستَخدِم المكتبة على ميّزة التحقّق الصارم من النوع ‎null‎ في TypeScript (باستخدام الخيار ‎strictNullChecks‎). لأنّ المعاملات التي لا تحدَّد تظهر على شكل ‎undefined‎ في JavaScript، فعادةً ما يُسمَح بتمرير القيمة ‎undefined‎ بصراحةٍ إلى دالة تقبل معاملات اختياريّة. هذه الشيفرة على سبيل المثال سليمة أثناء التحقق الصارم من النوع ‎null‎:

var x: Example;
// عند الكتابة بالأحمال الزائدة، فسنحصل بدون قصد على خطأ بسبب تمرير القيمة
// 'undefined'
// إلى النوع
// 'string'

// عند الكتابة بالمعاملات الاختيارية، فلن يحدث أي خطأ (وهذا هو المقصود)
x.diff("something", true ? undefined : "hour");

استعمل أنواع الاتحاد (Union Types)

لا تكتب أحمالًا زائدةً تختلف فقط في مكان نوع معاملٍ واحد فقط:

/* خطأ */
interface Moment {
    utcOffset(): number;
    utcOffset(b: number): Moment;
    utcOffset(b: string): Moment;
}

استعمل أنواع الاتحاد حيثما أمكن ذلك:

/* صحيح */
interface Moment {
    utcOffset(): number;
    utcOffset(b: number|string): Moment;
}

لاحظ أنّنا لم نجعل المعامل ‎b‎ اختياريًّا هنا لأنّ النوعين المعادين في التوقيعين مختلفان.

السبب: هذا مهمّ لمن يُمرّر قيمةً إلى دالّتك:

function fn(x: string): void;
function fn(x: number): void;
function fn(x: number|string) {
    // عند كتابتها بأحمال زائدة مختلفة، فسيحدث خطأ دون قصد
    // عند كتابتها بأنواع الاتحاد، فلن يحدث أي خطأ (وهذا ما نريده)
    return moment().utcOffset(x);
}

مصادر