التحقق من الأنواع في ملفات JavaScript في TypeScript
مقدمة
أصبحت TypeScript منذ النسخة 2.3 تدعم التحقق من الأنواع والإبلاغ عن الأخطاء في ملفّات .js
مع خيار المترجم --checkJs
.
يمكنك تخطي التحقق من ملفّاتٍ معيّنة عبر إضافة التعليق // @ts-nocheck
إليها؛ وفي المقابل يمكنك اختيار ملفّات .js
التي تريد التحقق منها عبر إضافة التعليق // @ts-check
إليها دون استخدام الخيار --checkJs
. يمكنك كذلك تجاهل الأخطاء على أسطرٍ محدَّدة عبر إضافة التعليق // @ts-ignore
على نفس السطر. لاحظ أنّه عند ضبط ملفّ tsconfig.json
، فالتحقق من ملفات JavaScript سيحترم الخيارات الصارمة مثل strictNullChecks
، و noImplicitAny
، وغيرها. لكن بسبب أنّ التحقق من ملفّات JavaScript ضعيف نسبيًّا فقد يكون دمج الخيارات الصارمة معها مفاجئًا، لذا استخدمها بحذر.
إليك بعض الفروقات بين التحقق من الأنواع في ملفّات .js
وفي ملفّات .ts
:
الفروقات بين التحقق من أنواع ملفّات .js
وملفّات .ts
تُستخدَم أنواع JSDoc للحصول على معلومات الأنواع
يُمكن أن تُستنتَج الأنواع في ملفّات .js
كما تُستنتَج في ملفّات .ts
. وعندما لا يمكن استنتاج الأنواع، فيُمكن تحديدها باستخدام JSDoc بنفس الطريقة التي تُستَخدَم فيها حواشي الأنواع (type annotations) في ملفّات .ts
، وكما في Typescript، سيُطلق الخيار --noImplicitAny
أخطاءً في الأماكن التي لا يمكن فيها استنتاج النوع. (باستثناء قيم الكائنات الحرفية المفتوحة [open-ended object literals]. انظر أدناه لتفاصيل أكثر).
تُستخدَم حواشي JSDoc التي تُزيِّن تصريحًا لضبط نوعه كما يلي:
/** @type {number} */
var x;
x = 0; // مسموح
x = false; // خطأ، لا يمكن تعيين قيمة منطقيّة لمتغيّرٍ نوعُه عددٌ
انظر أدناه لقائمةٍ كاملةٍ تحتوي على أنماط JSDoc المدعومة.
تُستنتَج الخاصيّات من التعيينات في جسم الصنف
لا يوجد طريقة للتصريح عن الخاصيات على الأصناف في النسخة ES2015. تُعيَّن الخاصيات ديناميكيًّا مثلها مثل قيم الكائنات الحرفيّة.
يستنتج المترجم الخاصيات في ملفّات .js
من تعيينات الخاصيات داخل جسم الصنف. نوعُ الخاصيات يكون النوعَ المعطى في الدالة البانية، إلا إذا لم يُحدَّد فيها، أو أن النوع المحدد في الدالة البانيّة هو undefined
أو null
. في هذه الحالة، يكون النوع هو اتحاد أنواع جميع القيم في يمين التعيينات. يُفترَض دائمًا بأن الخاصيات المُعرَّفة في الدالة البانيّة موجودة، أما تلك المعرفة في التوابع فقط أو في دوال الوصول (getters) أو الضبط (setters) فتُعَدّ اختيارية.
class C {
constructor() {
this.constructorOnly = 0
this.constructorUnknown = undefined
}
method() {
// خطأ، الخاصيّة
// constructorOnly
// عددٌ وليست قيمة منطقيّة
this.constructorOnly = false
// مسموح به، الخاصيّة
// constructorUnknown
// من النوع
// string | undefined
this.constructorUnknown = "plunkbat"
// مسموح به، لكنّ الخاصيّة
// methodOnly
// قد تكون كذلك من النوع
// undefined
this.methodOnly = 'ok'
}
method2() {
// مسموح به كذلك
// أصبح نوع الخاصيّة
// methodOnly
// string | boolean | undefined
this.methodOnly = true
}
}
إذا لم تُضبَط الخاصيات في جسم الصنف أبدًا (لا في الدالة البانية ولا في غيرها)، فإنّها تُعدّ مجهولة. إذا كان الصنف الخاص بك يحتوي على خاصيّات يُقرأ منها فقط، فأضف تصريحًا في الدالة البانيّة وأضف حاشية له باستخدام JSDoc لتحديد النوع. ولا تحتاج إلى إعطاء قيمة إذا كانت ستُهيّأ لاحقًا:
class C {
constructor() {
/** @type {number | undefined} */
this.prop = undefined;
/** @type {number | undefined} */
this.count;
}
}
let c = new C();
c.prop = 0; // مسموح
// خطأ، لا يمكن تعيين سلسلة نصيّة لخاصية من النوع
// number|undefined
c.count = "string";
الدوال البانية مكافئة للأصناف
قبل النسخة ES2015، كانت Javascript تعتمد على الدوال البانيّة عوضًا عن الأصناف. يدعم المترجم هذا النمط ويَعُدّ الدوال البانيّة مكافئةً لأصناف ES2015. تعمل قواعد استنتاج أنواع الخاصيات الموصوفة أعلاه بنفس الطريقة:
function C() {
this.constructorOnly = 0
this.constructorUnknown = undefined
}
C.prototype.method = function() {
this.constructorOnly = false // خطأ
// مسموح، أصبح النوع الآن
// string | undefined
this.constructorUnknown = "plunkbat"
}
وحدات CommonJS مدعومة كذلك
في ملفّات .js
، تفهم Typescript شكل وحدات CommonJS. يُتعرَّف على التعيينات لكل من exports
وmodule.exports
على أنّها تصريحات تصدير (export declarations). وبالمثل، يُتعرَّف على استدعاءات require
على أنّها استيرادات وحدات. على سبيل المثال:
// مكافئ للاستيراد `import module "fs"`
const fs = require("fs");
// مكافئ للتصدير `export function readFile`
module.exports.readFile = function(f) {
return fs.readFileSync(f);
}
دعم الوحدات في Javascript متسامح أكثر من دعم الوحدات في Typescript. إذ تكون معظم تراكيب التعيينات والتصريحات مدعومة.
الأصناف والدوال وقيم الكائنات الحرفيّة بمثابة مجالات الأسماء (namespaces)
الأصناف مجالاتُ أسماءٍ في ملفّات .js
. يُمكن استخدام هذه الميّزة لإنشاء أصناف متداخلة، على سبيل المثال:
class C {
}
C.D = class {
}
ولشيفرة ما قبل ES2015، يُمكن استخدامها لمحاكاة التوابع الساكنة:
function Outer() {
this.y = 2
}
Outer.Inner = function() {
this.yy = 2
}
يمكن كذلك استخدامها لإنشاء مجالات أسماء بسيطة:
var ns = {}
ns.C = class {
}
ns.func = function() {
}
يُمكن كذلك استعمال أشكال مختلفة:
// IIFE
var ns = (function (n) {
return n || {};
})();
ns.CONST = 1
// الرجوع افتراضيا للمجال العام
var assign = assign || function() {
// ضع شيفرتك هنا
}
assign.extra = 1
قيم الكائنات الحرفيّة مفتوحة
في ملفّات .ts
، تُعطي قيم الكائنات الحرفيّة التي تُهيّئُ تصريح متغيرٍ نوعَها للتصريح. لا يمكن إضافة أية عناصر جديدة لم تُحدَّد في قيمة الكائن الحرفيّة الأصلية. هذه القاعدة غير موجودة في ملفّات .js
؛ إذ تملك قيم الكائنات الحرفيّة نوعًا مفتوحًا (توقيع فهرس [index signature]) يسمح بإضافة الخاصيّات والبحث عنها ولو لم تكن مُعرَّفة أصلًا. على سبيل المثال:
var obj = { a: 1 };
obj.b = 2; // مسموح
تتصرّف قيم الكائنات الحرفيّة كما لو امتلكت توقيع الفهرس [x:string]: any
ما يسمح لها بأن تُعامَل على أنّها خرائط (maps) مفتوحة عوضًا عن كونها كائنات مغلقة.
يُمكن تغيير هذا السلوك كما يمكن تغيير سلوكات JavaScript الأخرى عبر تحديد نوع JSDoc للمتغيّر، على سبيل المثال:
/** @type {{a: number}} */
var obj = { a: 1 };
// خطأ، النوع
// {a: number}
// لا يملك الخاصيّةَ
// b
obj.b = 2;
تُعَدّ كل من null
وundefined
ومهيّئات المصفوفات الفارغة على أنّها من النوع any
أو any[]
أيّ متغيّرٍ أو معامل أو خاصيّة تُهيّأ بالقيمة null
أو القيمة undefined
ستملك النوعَ any
، حتى ولو كان التحقق الصارم من null
مفعّلًا (strict null checks). وكلّ متغيّر أو معامل أو خاصيّة تهيأ بالقيمة []
ستملك النوع any[]
، حتى ولو فُعّل التحقق الصارم من null
. الاستثناء الوحيد هنا هو الخاصيات التي تملك عدّة مُهيِّئات كما هو موضّح أدناه:
function Foo(i = null) {
if (!i) i = 1;
var j = undefined;
j = 2;
this.l = [];
}
var foo = new Foo();
foo.l.push(foo.i);
foo.l.push("end");
تكون معاملات الدوال اختيارية افتراضيا
تُعدّ جميع معاملات الدوال في ملفّات .js
اختياريةً بسبب عدم وجود طريقة لتحديد اختياريةِ المعاملات في نسخ JavaScript التي سبقت النسخة ES2015. يُسمَح باستدعاء الدوال بعدد معاملات أقلّ من عدد المعاملات المصرّح عنها في تعريف الدالة.
لاحظ أنّ استدعاء دالة بعدد زائد من المعاملات خطأ.
على سبيل المثال:
function bar(a, b) {
console.log(a + " " + b);
}
bar(1); // مسموح، يعدّ المعامل الثاني اختياريًّا
bar(1, 2);
bar(1, 2, 3); // خطأ، عدد زائد من المعاملات
الدوال ذات حواشي JSDoc مستثناة من هذه القاعدة. استخدم بنية المعاملات الاختيارية في JSDoc للتعبير عن الاختيارية:
/**
* @param {string} [somebody] – اسمُ شخص ما
*/
function sayHello(somebody) {
if (!somebody) {
somebody = 'John Doe';
}
console.log('Hello ' + somebody);
}
sayHello();
يُستنتَج تصريح Var-arg (متغيّر-معامل) من طريقة استعمال الكلمة المفتاحية arguments
إذا تواجدت في جسم دالةٍ ما إشارةٌ إلى الكلمة المفتاحية arguments
فستُعَد الدالة ذات معامل var-arg (مثل (...arg: any[]) => any
). استعمل بنية var-arg في JSDoc لتحديد نوع المعاملات:
/** @param {...number} args */
function sum(/* numbers */) {
var total = 0
for (var i = 0; i < arguments.length; i++) {
total += arguments[i]
}
return total
}
معاملات الأنواع التي لا تُحدَّد تكون افتراضيًّا النوعَ any
بسبب عدم وجود طريقة لتحديد معاملات الأنواع المعمّمة في JavaScript، فإنّ معاملات الأنواع التي لا تُحدَّد تكون افتراضيًّا النوعَ any
.
في جملة extends
على سبيل المثال، يُعرَّف React.Component
على أنّ له معاملا أنواعٍ، وَهُمَا Props
وState
. أمّا في ملفّات .js
فما من سبيل لتحديدها في جملة extends
. ستكون معاملات الأنواع افتراضيًا النوعَ any
:
import { Component } from "react";
class MyComponent extends Component {
render() {
// مسموح به لأنّ
// this.props
// من النوع
// any
this.props.b;
}
}
استعمل بنية @augments
في JSDoc لتحديد الأنواع صراحةً. على سبيل المثال:
import { Component } from "react";
/**
* @augments {Component<{a: number}, State>}
*/
class MyComponent extends Component {
render() {
// خطأ، الخاصيّة
// b
// غير موجودة على
// {a:number}
this.props.b;
}
}
في مراجع JSDoc
عند عدم تحديد نوع المعامل في JSDoc فالنوع يكون افتراضيا النوعَ any
:
/** @type{Array} */
var x = [];
x.push(1); // مسموح
// مسموح
// x
// من النوع
// Array<any>
x.push("string");
/** @type{Array.<number>} */
var y = [];
y.push(1); // مسموح
y.push("string"); // خطأ لا يمكن تعيين سلسلة نصيّة لمصفوفة أعداد
في استدعاءات الدوال
تُستعمَل المعاملات عند استدعاء دالة معمّمة لاستنتاج معاملات الأنواع. تفشل هذه العمليّة أحيانًا في محاولة استنتاج الأنواع، قد يحدث هذا في الأساس بسبب انعدام مصادر الاستنتاج (inference sources)؛ في هذه الحالات تكون معاملات الأنواعِ النوعَ any
افتراضيًا. على سبيل المثال:
var p = new Promise((resolve, reject) => { reject() });
p; // Promise<any>;
بنيات JSDoc المدعومة
تُوضّح القائمة أدناه البنيات المدعومة حاليًّا عند استخدام حواشي JSDoc لتوفير معلومات الأنواع في ملفّات JavaScript.
لاحظ أنّ أيّ وسوم غير موجودة وجودًا صريحًا أدناه ليست مدعومةً بعدُ (مثل @async
).
-
@type
-
@param
(أو @arg
أو @argument
) -
@returns
(أو @return
) -
@typedef
-
@callback
-
@template
-
@class
(أو @constructor
) -
@this
-
@extends
(أو @augments
) -
@enum
عادة ما يكون المعنى مطابقًا لمعنى الوسم الموجود في موقع JSDoc أو قد يكون مجموعةً عليا (superset) من معنى الوسم. تصِف الأقسام أدناه الفروقات وتعطي أمثلة على كيفيّة استخدام كل وسم:
@type
يمكنك استعمال الوسم @type
مع الإشارة إلى اسم نوعٍ (سواءً أكان نوعًا أوليًّا، أو نوعًا معرّفًا في تصريح TypeScript، أو في وسم @typedef
الخاصّ بنظام JSDoc). يمكنك استخدام أي نوعٍ من أنواع TypeScript، ويُمكنك كذلك استخدام معظم أنواع JSDoc:
/**
* @type {string}
*/
var s;
/** @type {Window} */
var win;
/** @type {PromiseLike<string>} */
var promisedString;
// يمكنك تحديد عنصر
// HTML
// لخاصيّات
// DOM
/** @type {HTMLElement} */
var myElement = document.querySelector(selector);
element.dataset.myData = '';
يُمكن تحديد نوع اتحادٍ باستخدام @type
، على سبيل المثال، يمكن لمتغيّر أن يحمل إمّا سلسلة نصيّة أو قيمة منطقيّة:
/**
* @type {(string | boolean)}
*/
var sb;
لاحظ أنّ الأقواس اختيارية في أنواع الاتحاد:
/**
* @type {string | boolean}
*/
var sb;
يمكنك تحديد أنواع المصفوفات باستخدام عدّة أنواع من البنيات (syntaxes):
/** @type {number[]} */
var ns;
/** @type {Array.<number>} */
var nds;
/** @type {Array<number>} */
var nas;
يمكنك كذلك تحديد أنواع قيم الكائنات الحرفيّة. على سبيل المثال، سيستعمل كائنٌ ذو الخاصيّة النصيّة a
والخاصيّة العدديّة b
البنية التالية:
/** @type {{ a: string, b: number }} */
var var9;
يمكنك تحديد الكائنات التي تشبه الخرائط (map-like) وتلك التي تشبه المصفوفات (array-like) باستخدام تواقيع فهرس نصيّة وعدديّة، يُمكنك استخدام كل من بنية JSDoc أو بنية Typescript:
/**
* كائن شبيه بالخرائط يربط خاصيّات نصيّة بأعداد
*
* @type {Object.<string, number>}
*/
var stringToNumber;
/** @type {Object.<number, object>} */
var arrayLike;
النوعان السابقان مكافئان للنوعين { [x: string]: number }
و{ [x: number]: any }
في TypeScript. وسيفهم المترجم كلا البنيتين.
يُمكنك تحديد أنواع الدوال باستخدام إمّا بنية لغة Typescript أو بنية لغة Closure:
/** @type {function(string, boolean): number} Closure syntax */
var sbn;
/** @type {(s: string, b: boolean) => number} Typescript syntax */
var sbn2;
أو يُمكنك كذلك استخدام النوع Function
غير المحدَّدِ الذي يشير إلى أنّ الكائن دالةٌ وحسب:
/** @type {Function} */
var fn7;
/** @type {function} */
var fn6;
هناك أنواع Closure أخرى تعمل كذلك:
/**
* @type {*} - يُمكن أن يكون أي نوع كيفما كان ('any')
*/
var star;
/**
* @type {?} - نوع مجهول (مكافئ له 'any')
*/
var question;
التحويلات (Casts)
استعارت Typescript بنية التحويلات من Closure. يسمح لك هذا بتحويل الأنواع إلى أنواع أخرى عبر إضافة وسم @type
قبل أي تعبير محصور بين قوسين (parenthesized):
/**
* @type {number | string}
*/
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString)
أنواع الاستيراد (Import types)
يمكنك كذلك استيراد تصريحاتٍ من ملفّات أخرى باستخدام أنواع الاستيراد. هذه البنية خاصّة بلغة Typescript وتختلف عن معيار JSDoc:
/**
* @param p { import("./a").Pet }
*/
function walk(p) {
console.log(`Walking ${p.name}...`);
}
يُمكن كذلك استخدام أنواع الاستيراد في تصريحات أسماء الأنواع البديلة (type alias declarations):
/**
* @typedef Pet { import("./a").Pet }
*/
/**
* @type {Pet}
*/
var myPet;
myPet.name;
يمكن استخدام أنواع الاستيراد للحصول على نوع قيمةٍ من وحدة إذا لم تعرف النوع، أو إن كان اسم النوع طويلًا ولم ترغب بكتابته:
/**
* @type {typeof import("./a").x }
*/
var x = require("./a").x;
@param
و@returns
يستعمل الوسم @param
نفس بنية الأنواع التي يستخدمها الوسم @type
، لكنّه يُضيف اسم مُعامل. يُمكن كذلك التصريح عن المعامل كمعاملٍ اختياري عبر إحاطة الاسم بأقواس مربّعة ([]):
// يمكن التصريح عن المعاملات بعدة أشكال من البنيات
/**
* @param {string} p1 – معامل نصيّ.
* @param {string=} p2 – معامل اختياري (Closure)
* @param {string} [p3] – معامل اختياريّ آخر (JSDoc).
* @param {string} [p4="test"] – معامل اختياري ذو قيمة افتراضية
* @return {string} هذه هي النتيجة
*/
function stringsStringStrings(p1, p2, p3, p4){
// TODO
}
وبالمثل، يمكن تحديد النوع المعاد لدالة كما يلي:
/**
* @return {PromiseLike<string>}
*/
function ps(){}
/**
* @returns {{ a: string, b: number }} - سواء '@returns' أو '@return'
*/
function ab(){}
@typedef
و@callback
و@param
يمكن استخدام @typedef
لتعريف أنواع معقّدة. والبنية تعمل بشكل مشابه للوسم @param
:
/**
* @typedef {Object} SpecialType – هذا ينشئ نوعًا جديدًا باسم 'SpecialType'
* @property {string} prop1 – خاصية نصيّة في النوع الجديد
* @property {number} prop2 – خاصيّة عدديّة في النوع الجديد
* @property {number=} prop3 – خاصيّة عدديّة اختيارية في النوع الجديد
* @prop {number} [prop4] - خاصيّة عدديّة اختيارية في النوع الجديد
* @prop {number} [prop5=42] - خاصيّة عدديّة اختيارية في النوع الجديد مع قيمة افتراضية
*/
// هنا نستعمل النوع الجديد الذي أنشأناه للتو
/** @type {SpecialType} */
var specialTypeObject;
يمكنك استخدام إمّا object
أو Object
على السطر الأول:
/**
* @typedef {object} SpecialType1 - هذا ينشئ نوعًا جديدًا باسم 'SpecialType1'
* @property {string} prop1 - خاصية نصيّة في النوع الجديد
* @property {number} prop2 - خاصيّة عدديّة في النوع الجديد
* @property {number=} prop3 - خاصيّة عدديّة اختيارية في النوع الجديد
*/
/** @type {SpecialType1} */
var specialTypeObject1;
يسمح @param
باستخدام بنية مشابهة لتحديد نوعٍ لاستخدامه مرّة واحدة. لاحظ أنّه ينبغي على الخاصيات المتداخلة أن تُسبَق باسم المعامل:
/**
* الشكل هو نفسه شكل النوع
* SpecialType
* أعلاه
*
* @param {Object} options
* @param {string} options.prop1
* @param {number} options.prop2
* @param {number=} options.prop3
* @param {number} [options.prop4]
* @param {number} [options.prop5=42]
*/
function special(options) {
return (options.prop4 || 1001) + options.prop5;
}
الوسم @callback
مشابه للوسم @typedef
، لكنّه يحدّد نوع دالةٍ عوضًا عن نوع كائنٍ:
/**
* @callback Predicate
* @param {string} data
* @param {number} [index]
* @returns {boolean}
*/
/** @type {Predicate} */
const ok = s => !(s.length % 2);
ويُمكن طبعًا التصريح عن أيٍّ من هذه الأنواع باستخدام بنية Typescript في سطر واحد بالوسم @typedef
:
/** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */
/** @typedef {(data: string, index?: number) => boolean} Predicate */
@template
يمكنك التصريح عن أنواعٍ معمّمة (generic types) بالوسم @template
:
/**
* @template T
* @param {T} p1 – معامل معمّم ينتقل إلى النوع المعاد
* @return {T}
*/
function id(x){ return x }
استعمل فاصلة أو عدّة وسوم للتصريح عن عدّة معاملاتِ أنواعٍ:
/**
* @template T,U,V
* @template W,X
*/
يمكنك كذلك تحديد قيد نوعٍ (type constraint) قبل اسم معامل النوع. معامل النوع الأول في القائمة هو وحده من يُقيَّد:
/**
* يجب على
* K
* أن يكون سلسلة نصيّة أو قيمة سلسلة نصيّة حرفيّة
* @template {string} K
*
* يجب أن يحتوي على تابع باسم
* "serious"
* @template {{ serious(): string }} Seriousalizable
* @param {K} key
* @param {Seriousalizable} object
*/
function seriousalize(key, object) {
// ????
}
@constructor
يستنتج المترجم الدوال البانيّة بناءً على التعيينات للخاصيّة this
، لكن يمكنك جعل التحقّق أكثر صرامةً ويُمكنك كذلك تحسين الاقتراحات بإضافة الوسم @constructor
:
/**
* @constructor
* @param {number} data
*/
function C(data) {
this.size = 0;
this.initialize(data); // ينبغي أن يُطلَق خطأ لأنّ المهيئ يتوقَّع سلسلةً نصيّة
}
/**
* @param {string} s
*/
C.prototype.initialize = function (s) {
this.size = s.length
}
var c = new C(0);
// يجب استدعاء
// C
// فقط بالكلمة المفتاحية
// new
var result = C(1);
باستخدام الوسم @constructor
، سيُتحقَّق من this
داخل الدالة البانيّة C
، لذا ستحصل على اقتراحات للتابع initialize
وستحصل على خطأ إذا مرّرت لها عددًا. ستحصل على خطأ كذلك إذا استدعيت C
عوضًا عن بنائها (بالكلمة المفتاحيّة new
).
لكن هذا يعني للأسف أنّ الدوال البانيّة القابلة للاستدعاء لا تستطيع استخدام الوسم @constructor
لأنّه يمنع الاستدعاء.
@this
يمكن عادةً للمترجم استنتاج نوعِ this
عندما يكون هناك سياق يساعد على ذلك. لكن عندما لا يستطيع المترجم استنتاج نوع this
، فتستطيع تحديده بالوسم @this
:
/**
* @this {HTMLElement}
* @param {*} e
*/
function callbackForLater(e) {
this.clientHeight = parseInt(e) // مسموح به
}
@extends
عندما تُوسِّع أصناف JavaScript صنفًا أساسًا مُعمَّمًا (generic base class)، فلا يوجد مكان لتحديد معامل النوع. يوفِّر الوسم @extends
مكانًا لمعامل الأنواع هذا:
/**
* @template T
* @extends {Set<T>}
*/
class SortableSet extends Set {
// ...
}
لاحظ أنّ الوسم @extends
يعمل فقط مع الأصناف. لا توجد طريقة حاليًّا لتوسيع صنفٍ من طرف دالّةٍ بانيّة.
@enum
يسمح الوسم @enum
بإنشاء قيمة كائن حرفيّة عناصرها من نوعٍ واحدٍ محدّد. ولا تسمح لعناصر أخرى بأن تكون في الكائن على النقيض من معظم قيم الكائنات الحرفيّة الموجودة في JavaScript.
/** @enum {number} */
const JSDocState = {
BeginningOfLine: 0,
SawAsterisk: 1,
SavingComments: 2,
}
لاحظ أنّ @enum
مختلفة عن الثوابت المتعدّدة enum
في Typescript، إذ أنّ @enum
أبسط وأكثر وضوحًا. وعلى النقيض من الثوابت المتعدّدة في Typescript، فالوسم @enum
قادرٌ على حمل أي نوع:
/** @enum {function(number): number} */
const Math = {
add1: n => n + 1,
id: n => -n,
sub1: n => n - 1,
}
المزيد من الأمثلة
var someObj = {
/**
* @param {string} param1 – تعمل الحواشي كذلك داخل تعيينات الخاصيات
*/
x: function(param1){}
};
/**
* وكذلك في تعيينات المتغيّرات
* @return {Window}
*/
let someFunc = function(){};
/**
* وتوابع الأصناف كذلك
* @param {string} greeting
*/
Foo.prototype.sayHi = (greeting) => console.log("Hi!");
/**
* وفي تعابير الدوال السهمية
* @param {number} x – دالّة جداء
*/
let myArrow = x => x * x;
/**
* ما يعني أنّها تعمل كذلك في مكونات الدوال عديمة الحالة في
* JSX
* @param {{a: string, b: number}} test – معاملٌ ما
*/
var sfc = (test) => <div>{test.a.charAt(0)}</div>;
/**
* يمكن لمعاملٍ أن يكون دالّة بانيّة باستخدام بنية لغة
* Closure
*
* @param {{new(...args: any[]): object}} C – الصنف المرغوب تسجيله
*/
function registerClass(C) {}
/**
* معامل بقيّة
* ('rest' arg)
* يُمثّل مصفوفة من السلاسل النصيّة، ويُعامَل كالنوع
* 'any'
*
* @param {...string} p1
*/
function fn10(p1){}
/**
* معامل بقيّة
* ('rest' arg)
* يُمثّل مصفوفة من السلاسل النصيّة، ويُعامَل كالنوع
* 'any'
*
* @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
*/
function fn9(p1) {
return p1.join();
}
الأنماط غير المدعومة
إنشاء مراجع تشير إلى كائنات في مكان القيمة كأنواعٍ لا يعمل إلا إذا كان الكائن يُنشئ كائنًا كذلك، مثل دالة بانيّة:
function aNormalFunction() {
}
/**
* @type {aNormalFunction}
*/
var wrong; // خطأ
/**
* استعمل
* 'typeof'
* كبديل
* @type {typeof aNormalFunction}
*/
var right; // صحيح
إلحاق المحرف =
إلى نوع خاصيّةٍ في قيمة كائن حرفيّةٍ لا يُحدِّد أنّ الخاصية اختيارية:
/**
* @type {{ a: string, b: number= }}
*/
var wrong; // خطأ
/**
* استعمل المحرف
* ?
* كبديل
* @type {{ a: string, b?: number }}
*/
var right; // صحيح
لا معنى للأنواع التي تقبل القيمة null
إلّا عند تفعيل الخيار strictNullChecks
:
/**
* @type {?number}
* عند تفعيل الخيار
* strictNullChecks: true -- number | null
* عند تعطيله
* strictNullChecks: off -- number
*/
var nullable;
لا معنى للأنواع التي لا تقبل القيمة null
وتُعامَل كما تُعامل أنواعها الأصليّة:
/**
* @type {!number}
*
* سيحمل النوع
* number
* فقط
*/
var normal;
على النقيض من نظام أنواع JSDoc، فلغة Typescript تسمح لك بتعليم (mark) الأنواع على أنّها تحمل القيمة null
أو لا. لا يوجد طريقة لإعلان أنّ النوع لا يقبل القيمة null
. إذا كان الخيار strictNullChecks
مفعّلًا، فعندها لا يمكن للنوع number
أن يحمل القيمة null
. إذا كان الخيار معطّلًا، فعندئذٍ سيقبل النوعُ number
القيمةَ null
.