تحميل البناء في TypeScript
هل قام أي شخص بتنفيذ التحميل الزائد للبناء في TypeScript؟ في الصفحة 64 من مواصفات اللغة (الإصدار 0.8)، هناك عبارات تصف التحميل الزائد للبناء، ولكن لم يتم تقديم أي مثال على الشيفرة.
أنا أجرب حالياً إعلان فئة أساسية جدًا؛ يبدو هكذا,
interface IBox {
x : number;
y : number;
height : number;
width : number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor(obj: IBox) {
this.x = obj.x;
this.y = obj.y;
this.height = obj.height;
this.width = obj.width;
}
constructor() {
this.x = 0;
this.y = 0;
this.width = 0;
this.height = 0;
}
}
عند التشغيل باستخدام tsc BoxSample.ts، يظهر خطأ بسبب تعريف مُنشئ مكرر -- وهذا واضح. أي مساعدة ستكون مقدرة.
الإجابة رقم 1
يسمح لك TypeScript بالإعلان عن التحميل الزائد ولكن يمكنك أن تملك تنفيذًا واحدًا فقط ويجب أن يكون لهذا التنفيذ توقيع متوافق مع جميع التحميلات الزائدة. في مثالِك، يمكن القيام بذلك بسهولة باستخدام معامل اختياري كما في,
interface IBox {
x: number;
y: number;
height: number;
width: number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor(obj?: IBox) {
this.x = obj?.x ?? 0;
this.y = obj?.y ?? 0;
this.height = obj?.height ?? 0;
this.width = obj?.width ?? 0;
}
}
أو اثنان من التحميل الزائد مع منشئ أكثر عمومية كما في،
interface IBox {
x: number;
y: number;
height: number;
width: number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
this.x = obj?.x ?? 0;
this.y = obj?.y ?? 0;
this.height = obj?.height ?? 0;
this.width = obj?.width ?? 0;
}
}
شاهد في الملعب
الإجابة رقم 2
بالنسبة لـ تحميلات الباني، فإن أحد البدائل الجيدة هو تنفيذ التحميلات الإضافية كـ طرق ثابتة لإنشاء الكائنات. أعتقد أنه أكثر قابلية للقراءة وأسهل من التحقق من جميع تراكيب الوسائط الممكنة في الباني.
في المثال التالي، يمكننا إنشاء كائن مريض باستخدام بيانات من مزود التأمين الذي يخزن القيم بشكل مختلف. لدعم هيكل بيانات آخر لإنشاء المريض، يمكن ببساطة إضافة طريقة ثابتة أخرى لاستدعاء البنّاء الافتراضي بأفضل ما يمكن بعد تطبيع البيانات المقدمة.
class Patient {
static fromInsurance({
first, middle = '', last,
birthday, gender
}: InsuranceCustomer): Patient {
return new this(
`${last}, ${first} ${middle}`.trim(),
utils.age(birthday),
gender
);
}
constructor(
public name: string,
public age: number,
public gender?: string
) {}
}
interface InsuranceCustomer {
first: string,
middle?: string,
last: string,
birthday: string,
gender: 'M' | 'F'
}
const utils = { /* included in the playground link below */};
{// Two ways of creating a Patient instance
const
jane = new Patient('Doe, Jane', 21),
alsoJane = Patient.fromInsurance({
first: 'Jane', last: 'Doe',
birthday: 'Jan 1, 2000', gender: 'F'
})
console.clear()
console.log(jane)
console.log(alsoJane)
}
يمكنك التحقق من المخرجات في TS Playground
تعدد تعريفات الدوال في TypeScript ليس حقيقيًا، لنفترض ذلك، لأنه سيتطلب الكثير من الكود الذي يولده المترجم وTypeScript مصمم لتجنب ذلك بأي تكلفة. الاستخدام الرئيسي لتعدد تعريفات الدوال هو على الأرجح كتابة الإعلانات للمكتبات التي تحتوي على وسائط سحرية في واجهة برمجة التطبيقات الخاصة بها. نظرًا لأن كل العمل الشاق للتعامل مع مجموعات مختلفة من الوسائط المحتملة يتم بواسطة المطور نفسه، لا أرى فائدة كبيرة في استخدام تعدد التعريفات بدلاً من الدوال المخصصة لكل سيناريو.
الإجابة رقم 3
يبدو أنك تريد أن يكون معامل الكائن اختياريًا، وأيضًا أن تكون كل خاصية من خصائص الكائن اختيارية. في المثال، كما هو موضح، ليست هناك حاجة إلى بناء جملة التحميل الزائد. أردت أن أشير إلى بعض الممارسات السيئة في بعض الإجابات هنا. بالطبع، هذا ليس أدنى تعبير ممكن لكتابة box={ x: 0, y: 87, width: 4, height: 0 }، لكنه يوفر كل الميزات المساعدة في الكتابة التي قد تحتاجها من الفصل كما هو موصوف. يسمح لك هذا المثال باستدعاء دالة بمعامل واحد، بعض المعاملات، جميعها، أو عدم وجود أي منها والحصول على القيم الافتراضية.
/** @class */
class Box {
public x?: number;
public y?: number;
public height?: number;
public width?: number;
// The Box class can work double-duty as the interface here since
// they are identical. If you choose to add methods or modify this class,
// you will need to define and reference a new interface
// for the incoming parameters object
// e.g.: `constructor(params: BoxObjI = {} as BoxObjI)`
constructor(params: Box = {} as Box) {
// Define the properties of the incoming `params` object here.
// Setting a default value with the `= 0` syntax is optional for each parameter
const {
x = 0,
y = 0,
height = 1,
width = 1
} = params;
// If needed, make the parameters publicly accessible
// on the class ex.: 'this.var = var'.
/** Use jsdoc comments here for inline ide auto-documentation */
this.x = x;
this.y = y;
this.height = height;
this.width = width;
}
}
هل تحتاج إلى إضافة طرق؟ بديل مطوّل لكنه أكثر قابلية للتوسع: يمكن لفئة Box أعلاه أن تقوم بوظيفة مزدوجة كواجهة نظرًا لأنه متطابق. إذا اخترت تعديل الفئة أعلاه، فستحتاج إلى تعريف واجهة جديدة والإشارة إليها لكائن معلمات الإدخال نظرًا لأن فئة Box لم تعد ستبدو تمامًا مثل معلمات الإدخال. لاحظ مكان تحريك علامات الاستفهام (?:) التي تشير إلى الخصائص الاختيارية في هذه الحالة. بما أننا نقوم بتعيين القيم الافتراضية داخل الفئة، فهي مضمونة التواجد، ومع ذلك فهي اختيارية داخل كائن معلمات الإدخال:
interface BoxParams {
x?: number;
// Add Parameters ...
}
class Box {
public x: number;
// Copy Parameters ...
constructor(params: BoxParams = {} as BoxParams) {
let { x = 0 } = params;
this.x = x;
}
doSomething = () => {
return this.x + this.x;
}
}
مهما كانت الطريقة التي تختارها لتعريف فصلك، فإن هذه التقنية توفر حدود الأمان للنوع، ومع ذلك تمنحك المرونة لكتابة أي من هذه:
const box1 = new Box();
const box2 = new Box({});
const box3 = new Box({x: 0});
const box4 = new Box({x: 0, height: 10});
const box5 = new Box({x: 0, y: 87, width: 4, height: 0});
// Correctly reports error in TypeScript, and in js, box6.z is undefined
const box6 = new Box({z: 0});
عند التجميع، ترى كيف يتم استخدام الإعدادات الافتراضية فقط إذا كانت القيمة الاختيارية غير محددة؛ حيث يتجنب ذلك مخاطر بناء جملة بديل شائع الاستخدام (لكنه عرضة للأخطاء) var=isOptional || default; من خلال التحقق مقابل void 0، والذي هو اختصار لـ undefined:
الناتج المُجمّع
var Box = (function () {
function Box(params) {
if (params === void 0) { params = {}; }
var _a = params.x, x = _a === void 0 ? 0 : _a, _b = params.y, y = _b === void 0 ? 0 : _b, _c = params.height, height = _c === void 0 ? 1 : _c, _d = params.width, width = _d === void 0 ? 1 : _d;
this.x = x;
this.y = y;
this.height = height;
this.width = width;
}
return Box;
}());
ملحق: تعيين القيم الافتراضية: الطريقة الخاطئة
المعامل || (أو)
ضع في اعتبارك خطر استخدام عاملَي ||/or عند تحديد قيم افتراضية احتياطية كما هو موضح في بعض الإجابات الأخرى. يوضح الكود أدناه الطريقة الخاطئة لتعيين الافتراضات. يمكنك الحصول على نتائج غير متوقعة عند التقييم مقابل القيم falsey مثل 0، ''، null، undefined، false، NaN:
var myDesiredValue = 0;
var result = myDesiredValue || 2;
// This test will correctly report a problem with this setup.
console.assert(myDesiredValue === result && result === 0, 'Result should equal myDesiredValue. ' + myDesiredValue + ' does not equal ' + result);
Object.assign(this, params)
في اختباراتِي، استخدام الكائن المفكك (destructured object) في es6/typescript يمكن أن يكون أسرع بنسبة 15-90% من Object.assign. استخدام معامل مفكك يسمح فقط بالطرق والخصائص التي قمت بتعيينها للكائن. على سبيل المثال، انظر إلى هذه الطريقة:
class BoxTest {
public x?: number = 1;
constructor(params: BoxTest = {} as BoxTest) {
Object.assign(this, params);
}
}
إذا كان مستخدم آخر لا يستخدم TypeScript وحاول وضع معلمة لا تنتمي، على سبيل المثال، قد يحاول وضع خاصية z
var box = new BoxTest({x: 0, y: 87, width: 4, height: 0, z: 7});
// This test will correctly report an error with this setup. `z` was defined even though `z` is not an allowed property of params.
console.assert(typeof box.z === 'undefined')
الإجابة رقم 4
لاحظ أنه يمكنك أيضًا تجاوز نقص التحميل الزائد على مستوى التنفيذ من خلال المعلمات الافتراضية في TypeScript، على سبيل المثال:
interface IBox {
x : number;
y : number;
height : number;
width : number;
}
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor(obj : IBox = {x:0,y:0, height:0, width:0}) {
this.x = obj.x;
this.y = obj.y;
this.height = obj.height;
this.width = obj.width;
}
}
تعديل: اعتبارًا من 5 ديسمبر 2016، انظر إجابة بنسون للحصول على حل أكثر تفصيلاً يسمح بمزيد من المرونة.
الإجابة رقم 5
التحديث 2 (28 سبتمبر 2020): هذه اللغة تتطور باستمرار، لذلك إذا كان بإمكانك استخدام Partial (المقدمة في الإصدار 2.1) فإن هذا أصبح الآن طريقتي المفضلة لتحقيق ذلك.
class Box {
x: number;
y: number;
height: number;
width: number;
public constructor(b: Partial<Box> = {}) {
Object.assign(this, b);
}
}
// Example use
const a = new Box();
const b = new Box({x: 10, height: 99});
const c = new Box({foo: 10}); // Will fail to compile
تحديث (8 يونيو 2017): قدم كل من guyarad و snolflake نقاطًا صحيحة في تعليقاتهما أدناه على إجابتي. أوصي القراء بمراجعة الإجابات التي قدمها Benson و Joe و snolflake والذين لديهم إجابات أفضل من إجابتي.
الإجابة الأصلية (27 يناير 2014)
مثال آخر على كيفية تحقيق التحميل الزائد للباني:
class DateHour {
private date: Date;
private relativeHour: number;
constructor(year: number, month: number, day: number, relativeHour: number);
constructor(date: Date, relativeHour: number);
constructor(dateOrYear: any, monthOrRelativeHour: number, day?: number, relativeHour?: number) {
if (typeof dateOrYear === "number") {
this.date = new Date(dateOrYear, monthOrRelativeHour, day);
this.relativeHour = relativeHour;
} else {
var date = <Date> dateOrYear;
this.date = new Date(date.getFullYear(), date.getMonth(), date.getDate());
this.relativeHour = monthOrRelativeHour;
}
}
}
المصدر: http://mimosite.com/blog/post/2013/04/08/Overloading-in-TypeScript
الإجابة رقم 6
أعلم أن هذا سؤال قديم، لكن الجديد في 1.4 هو أنواع الاتحاد؛ استخدم هذه لجميع التحميلات الزائدة للدوال (بما في ذلك البُناة). مثال:
class foo {
private _name: any;
constructor(name: string | number) {
this._name = name;
}
}
var f1 = new foo("bar");
var f2 = new foo(1);
الإجابة رقم 7
في الواقع قد يكون الوقت قد فات للإجابة على هذا، ولكن يمكنك الآن فعل هذا:
class Box {
public x: number;
public y: number;
public height: number;
public width: number;
constructor();
constructor(obj: IBox);
constructor(obj?: IBox) {
this.x = !obj ? 0 : obj.x;
this.y = !obj ? 0 : obj.y;
this.height = !obj ? 0 : obj.height;
this.width = !obj ? 0 : obj.width;
}
}
لذلك بدلاً من الطرق الثابتة يمكنك القيام بما سبق. آمل أن يساعدك هذا!!!
