Menu

TypeScript Function Overloading: חתימות overload

function overloads ב-TypeScript מאפשרים לפונקציה אחת כמה חתימות קריאה, לכל אחת טיפוס החזרה משלה. כאן תלמדו את התבנית של חתימות overload ומימוש אחד, את הכללים שהקומפיילר בודק, מתי פרמטר union עדיף, ו-overloads במחלקות.

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

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

בלי ה-overloads, parse היה מחזיר number | number[] בכל קריאה, ו-one + 1 היה שגיאה עד שהייתם מצמצמים את התוצאה בעצמכם.

חתימות overload והמימוש

לפונקציה עם overloads יש שני חלקים:

  1. חתימות overload: הצהרות בלי גוף, אחת לכל צורת קריאה נתמכת. אלה החתימות היחידות שמי שקורא לפונקציה יכול להשתמש בהן.
  2. חתימת המימוש: ההצהרה האחרונה, זו עם הגוף. הפרמטרים שלה חייבים לקבל את כל מה שה-overloads מקבלים, וטיפוס ההחזרה שלה חייב לכסות את טיפוס ההחזרה של כל overload. היא לא נראית מבחוץ.

טיפוסים קיימים רק בזמן הקומפילציה, ולכן בזמן ריצה יש פונקציית JavaScript אחת. המימוש צריך לבדוק את הארגומנטים שלו (typeof, Array.isArray, arguments.length...) כדי להחליט מה לעשות. הקומפיילר בודק שה-overloads והמימוש מתאימים זה לזה:

function format(value: string): string;
function format(value: number): number {
  return value;
}
// error TS2394: This overload signature is not compatible with its implementation signature.

הפתרון הוא להרחיב את המימוש: function format(value: string | number): string | number.

אי אפשר לקרוא לחתימת המימוש

זה הכלל שמפתיע הכי הרבה אנשים. קריאה צריכה להתאים בעצמה לאחת מחתימות ה-overload; TypeScript לא משלב ביניהן.

הקומפיילר מדפיס:

index.ts(12,19): error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type 'string | string[]' is not assignable to parameter of type 'string[]'.
      Type 'string' is not assignable to type 'string[]'.

המימוש מקבל string | string[], אבל מי שקורא לפונקציה לא רואה אותו. הוסיפו overload שלישי שמקבל את ה-union ומחזיר את ה-union, והקריאה מתקמפלת ומדפיסה [ 1, 2 ]:

function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]): number | number[];
function parse(input: string | string[]): number | number[] {
  return Array.isArray(input) ? input.map(Number) : Number(input);
}

מספר שונה של פרמטרים

overloads מתארים גם קריאות עם מספר שונה של ארגומנטים. כאן אפשר לבנות תאריך מ-timestamp או משנה, חודש ויום, אבל לא משני מספרים:

חתימה אחת עם שני פרמטרים אופציונליים הייתה מקבלת את makeDate(2024, 3) ובונה בשקט תאריך שגוי. ה-overloads הופכים את זה לשגיאת קומפילציה (TS2575).

הסדר חשוב

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

function describe(value: unknown): string;   // matches everything
function describe(value: string): "text";    // never chosen
function describe(value: unknown): string {
  return typeof value === "string" ? "text" : "other";
}

const d = describe("hi"); // d: string, not "text"

החליפו את הסדר של שתי החתימות הראשונות, ו-describe("hi") יקבל את הטיפוס "text".

overloads או פרמטר union?

overloads שווים את השורות הנוספות כשטיפוס ההחזרה תלוי בטיפוסי הארגומנטים. כשהוא לא תלוי, חתימה אחת עם פרמטר union קצרה יותר, קלה יותר לקריאה, ומקבלת ארגומנטים מסוג union ש-overloads היו דוחים.

השתמשו במתי
פרמטר unionאותו טיפוס החזרה לכל קלט
פרמטרים אופציונלייםצורות הקריאה נבדלות רק בארגומנטים בסוף שאפשר להשמיט בחופשיות
Overloadsטיפוס ההחזרה משתנה לפי הארגומנטים, או שצריך לדחות צירופים מסוימים של ארגומנטים
גנריטיפוס ההחזרה נבנה מטיפוס הארגומנט, כמו identity<T>(x: T): T

גנרי עם conditional type יכול לבטא חלק מקבוצות ה-overloads כחתימה אחת, אבל בשניים או שלושה מקרים overloads בדרך כלל קלים יותר לקריאה.

מתודות ובנאים עם overloads

מתודות משתמשות באותה תבנית בתוך מחלקה: חתימות overload, ואחריהן המתודה עם הגוף. אפשר לעשות overload לבנאים באותה דרך.

גם interfaces וטיפוסי אובייקט יכולים להצהיר על overloads, בתור כמה חתימות קריאה או כמה חתימות מתודה עם אותו שם. הרבה פונקציות מובנות מוצהרות כך: רחפו עם העכבר מעל reduce של מערך בעורך, ותראו "+2 overloads".

שאלות נפוצות

האם TypeScript תומך ב-function overloading?

כן, ברמת הטיפוסים. כותבים כמה חתימות overload (הצהרות בלי גוף) ואחריהן מימוש אחד. מי שקורא לפונקציה רואה רק את חתימות ה-overload. בזמן ריצה עדיין יש רק פונקציית JavaScript אחת, ולכן המימוש בודק בעצמו את הארגומנטים ומטפל בכל מקרה.

מה המשמעות של "No overload matches this call"?

השגיאה TS2769: הארגומנטים לא מתאימים לאף אחת מחתימות ה-overload. חתימת המימוש לא נחשבת, ולכן קריאה עם ארגומנט מסוג union כמו string | string[] נכשלת גם כשהמימוש מקבל אותו. הוסיפו overload שמקבל את ה-union, או החליפו את ה-overloads בחתימה אחת.

מתי כדאי להשתמש ב-overloads במקום בטיפוס union?

השתמשו ב-overloads כשטיפוס ההחזרה תלוי בטיפוסי הארגומנטים שמועברים, למשל string בכניסה נותן number ביציאה, אבל string[] בכניסה נותן number[] ביציאה. כשטיפוס ההחזרה זהה לכל קלט, חתימה אחת עם פרמטר union פשוטה יותר וגם מקבלת ארגומנטים מסוג union.

אפשר לעשות overload לפונקציות חץ ב-TypeScript?

לא בתחביר ההצהרה של overloads, שעובד רק על הצהרות function ועל מתודות. אפשר לתת למשתנה טיפוס עם כמה חתימות קריאה, type Parse = { (s: string): number; (s: string[]): number[] }, אבל השמה של פונקציית חץ אליו בדרך כלל דורשת type assertion, ולכן הצהרת function היא הבחירה הנקייה יותר.

למה חתימת ה-overload שלי לא תואמת לחתימת המימוש?

השגיאה TS2394 אומרת ש-overload אחד מקבל או מחזיר משהו שהמימוש לא מקבל או מחזיר. הפרמטרים של המימוש חייבים לקבל את הפרמטרים של כל overload, וטיפוס ההחזרה שלו חייב להיות תואם לטיפוס ההחזרה של כל overload. הרחבה של המימוש (לרוב ל-union) פותרת את זה.

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

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

להתחיל