Menu

Constructor ב-TypeScript: Parameter Properties ו-Overloads

איך נותנים טיפוסים לבנאי של מחלקה ב-TypeScript: פרמטרים עם טיפוסים ופרמטרים אופציונליים, parameter properties כמו constructor(private name: string), סדר האתחול של שדות, overloads של בנאים, קריאות super בתתי-מחלקות, בנאים פרטיים וטיפוסי בנאי עם new.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

בנאי ב-TypeScript הוא המתודה constructor של המחלקה, עם פרמטרים שיש להם טיפוסים. אין לו הערת טיפוס החזרה: הוא תמיד מייצר מופע של המחלקה.

כתיבה של constructor(): Product היא השגיאה TS1093 (Type annotation cannot appear on a constructor declaration). כל השאר באופן שבו new עובד הוא JavaScript רגיל.

Parameter Properties

הדפוס שלמעלה (להצהיר על שדה, לקבל פרמטר, להעתיק אותו) נפוץ כל כך שיש ל-TypeScript קיצור. שימו public, private, protected או readonly לפני פרמטר של בנאי, והוא הופך לשדה:

זו אחת התכונות הבודדות של TypeScript שמייצרות קוד. הקומפיילר כותב בשבילכם את ההשמות:

class Product {
    name;
    price;
    sku;
    constructor(name, price, sku) {
        this.name = name;
        this.price = price;
        this.sku = sku;
    }
    // ...
}

מכיוון שזה לא רק טיפוסים שנמחקים, parameter properties לא רצים תחת ה-type stripping המובנה של Node (node file.ts נכשל עם ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX), ואפשרות הקומפיילר erasableSyntaxOnly מדווחת עליהם כשגיאה TS1294. פרויקטים שמריצים קבצי .ts ישירות כותבים את השדות ביד.

פרמטרים אופציונליים וברירות מחדל

פרמטרים של בנאי פועלים לפי אותם כללים כמו פרמטרים של פונקציה: ? הופך פרמטר לאופציונלי, ערך ברירת מחדל הופך אותו לאופציונלי וממלא אותו, ופרמטרים חובה באים קודם.

גם ל-parameter properties יכולות להיות ברירות מחדל. הטיפוס של color בתוך המחלקה הוא string | undefined, ולכן קוד שמשתמש בו צריך לבדוק קודם undefined.

לבנאים עם הרבה אפשרויות, אובייקט אפשרויות אחד קריא יותר מרשימה ארוכה של פרמטרים לפי מיקום: constructor(opts: { prefix: string; level?: "info" | "debug" }).

סדר האתחול של שדות

ערכים התחלתיים של שדות מחושבים לפני גוף הבנאי, ו-parameter properties מקבלים ערך בתחילת גוף הבנאי. לכן ערך התחלתי של שדה לא יכול לקרוא parameter property. TypeScript תופסת את זה:

index.ts(3,18): error TS2729: Property 'size' is used before its initialization.
index.ts(3,30): error TS2729: Property 'size' is used before its initialization.

בזמן ריצה הערך ההתחלתי היה רואה undefined ומייצר NaN. במקום זה, חשבו את הערך בגוף הבנאי:

class Grid {
    cells: number;

    constructor(public size: number) {
        this.cells = size * size;
    }
}

console.log(new Grid(3).cells); // 9

Overloads של בנאים

למחלקה יש בדיוק מימוש אחד של בנאי, אבל אפשר לפרט מעליו כמה חתימות overload. הקוראים רואים רק את ה-overloads; חתימת המימוש חייבת להיות תואמת לכולן.

overloads נהיים קשים לקריאה אחרי שתיים או שלוש צורות. מתודות factory סטטיות עם שמות תיאוריים (Color.fromHex("#ff8800"), Color.fromRgb(10, 20, 30)) לרוב ברורות יותר, ולא צריכות צמצום טיפוסים בתוך גוף אחד.

קריאה ל-super בתת-מחלקה

מחלקה נגזרת שמצהירה על בנאי משלה חייבת לקרוא ל-super(...) עם הארגומנטים של מחלקת האב, ולעשות את זה לפני שהיא נוגעת ב-this. שכחת הקריאה היא השגיאה TS2377 (Constructors for derived classes must contain a 'super' call), ושימוש ב-this קודם הוא TS17009 ('super' must be called before accessing 'this' in the constructor of a derived class). שתיהן משקפות כללי זמן ריצה של JavaScript.

class Animal {
    constructor(public name: string) {}
}

class Dog extends Animal {
    tricks: string[];

    constructor(name: string, ...tricks: string[]) {
        super(name); // must come first
        this.tricks = tricks;
    }
}

const rex = new Dog("Rex", "sit", "roll");
console.log(rex); // Dog { name: 'Rex', tricks: [ 'sit', 'roll' ] }

תת-מחלקה שלא מוסיפה בנאי יורשת את הבנאי של מחלקת האב, עם אותם טיפוסי פרמטרים. עוד על תתי-מחלקות בירושה.

בנאים פרטיים והכנה אסינכרונית

אפשר לקרוא לבנאי private רק מתוך המחלקה. מבחוץ, new הוא השגיאה TS2673. כך מכריחים את הקוראים לעבור דרך factory, בשביל singleton או בשביל הכנה שחייבת להיות אסינכרונית (בנאים לא יכולים להיות async).

private כאן הוא כלל של זמן קומפילציה בלבד. ב-JavaScript שנוצר יש בנאי רגיל, ולכן קוד JavaScript רגיל עדיין יכול לקרוא ל-new Connection(...).

טיפוסי בנאי

כדי לקבל מחלקה כערך, תנו לפרמטר טיפוס עם construct signature: new (...args) => Instance. גם typeof MyClass עובד, אבל הוא קושר את הפרמטר לחתימת הבנאי ולאיברים הסטטיים של המחלקה הספציפית הזו.

ConstructorParameters ו-InstanceType מחלצים את ה-tuple של הפרמטרים ואת טיפוס המופע מטיפוס של בנאי; הם מוסברים יחד עם שאר עוזרי הפונקציות ב-ReturnType ו-Parameters.

שאלות נפוצות

מה הם parameter properties ב-TypeScript?

פרמטר של בנאי עם מגדיר (public, private, protected או readonly) מצהיר על שדה ומשים לו ערך בצעד אחד. constructor(private name: string) {} הוא קיצור של הצהרה על private name: string וכתיבה של this.name = name. הקומפיילר מייצר את ההשמה הזו בפלט.

האם למחלקה ב-TypeScript יכולים להיות כמה בנאים?

לא כמה מימושים. למחלקה יש בנאי אחד, אבל אפשר לכתוב מעליו כמה חתימות overload, כך שהקוראים רואים רשימות פרמטרים שונות. מתודות factory סטטיות כמו Color.fromHex() ו-Color.fromRgb() לרוב ברורות יותר מ-overloads.

האם בנאי יכול להיות async ב-TypeScript?

לא. בנאי תמיד מחזיר את המופע החדש, אף פעם לא promise, ו-async constructor() נדחה עם השגיאה TS1089 ('async' modifier cannot appear on a constructor declaration). השתמשו בבנאי פרטי יחד עם מתודה סטטית async create() שעושה את העבודה האסינכרונית ואז קוראת ל-new.

איך נותנים טיפוס לבנאי של מחלקה כפרמטר?

השתמשו ב-construct signature: new (name: string) => User, או ב-typeof User למחלקה המדויקת הזו. factory גנרי נראה כך: function make<T>(ctor: new () => T): T { return new ctor(); }. ConstructorParameters<typeof User> נותן את רשימת הפרמטרים כ-tuple.

למה מקבלים "'super' must be called before accessing 'this'"?

זו השגיאה TS17009. במחלקה שעושה extends למחלקה אחרת, הבנאי של מחלקת האב יוצר את האובייקט, ולכן this לא קיים עד ש-super(...) רץ. העבירו את הקריאה ל-super מעל כל שורה שנוגעת ב-this.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל