Menu

TypeScript extends: הרחבת interface או type

מילת המפתח extends בונה טיפוס אחד מתוך טיפוס אחר. כאן תלמדו להרחיב interface (אחד או כמה), להרחיב type alias עם &, לדרוס את הטיפוס של מאפיין, להחליף מאפיינים עם Omit, ומה המשמעות של extends במחלקות, באילוצים גנריים וב-conditional types.

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

interface Child extends Parent יוצר interface חדש עם כל האיברים של Parent ועם האיברים שלו עצמו. אפשר להשתמש בערכים מטיפוס הילד בכל מקום שבו מצופה ההורה, כי יש להם את כל המאפיינים שלו.

אותה מילת מפתח מופיעה ב-TypeScript בארבעה מקומות, עם משמעויות קרובות אבל שונות:

איפהדוגמהמשמעות
Interfaceinterface Dog extends Animal {}ירושת האיברים של ההורה
מחלקהclass Dog extends Animal {}ירושת שדות ומימושי מתודות (JavaScript בזמן ריצה)
פרמטר גנרי<T extends Animal>אילוץ: ארגומנט הטיפוס חייב להיות ניתן להשמה ל-Animal
Conditional typeT extends string ? A : Bבדיקה: האם T ניתן להשמה ל-string?

הרחבה של כמה interfaces

כתבו כמה הורים אחרי extends, מופרדים בפסיקים. לתוצאה יש את האיברים של כולם:

interface Identified {
  id: number;
}

interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface Post extends Identified, Timestamped {
  title: string;
}

// Post requires id, createdAt, updatedAt and title

אם שני הורים מצהירים על אותו מאפיין עם טיפוסים שאינם זהים, הילד הוא השגיאה TS2320 (Interface 'Post' cannot simultaneously extend types 'A' and 'B'), אלא אם הילד מצהיר מחדש על המאפיין הזה עם טיפוס שניתן להשמה לשניהם.

הרחבה של type alias

ל-type alias אין פסוקית extends. בונים עליו עם intersection, &, שמשלב את האיברים של שני הצדדים. גם interface יכול להרחיב ישירות type alias של אובייקט, ו-type alias יכול ליצור intersection עם interface: שני הסוגים משתלבים בחופשיות.

שתי הדרכים מתנהגות אותו דבר עד שיש התנגשות בין מאפיינים: extends מדווח על מאפיין מתנגש כבר בהצהרה, ואילו & הופך אותו בשקט ל-never. ההשוואה נמצאת בעמוד interface מול type, והפרטים על & נמצאים בעמוד על intersection types.

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

interface ילד יכול להצהיר מחדש על מאפיין של ההורה, אבל רק עם טיפוס שניתן להשמה לטיפוס של ההורה. מותר לצמצם string לליטרל, או להסיר null מ-union. אסור לשנות את הטיפוס למשהו שאינו קשור:

index.ts(13,11): error TS2430: Interface 'Label' incorrectly extends interface 'Shape'.
  Types of property 'kind' are incompatible.
    Type 'number' is not assignable to type 'string'.

הכלל קיים כדי שעדיין יהיה אפשר להשתמש ב-Label בכל מקום שבו מצופה Shape. אם kind היה יכול להיות מספר, קוד שנכתב עבור Shape וקורא ל-shape.kind.toUpperCase() היה נשבר.

החלפת מאפיינים עם Omit

כשבאמת צריך טיפוס אחר למאפיין שעובר בירושה, הסירו אותו קודם מההורה עם Omit, ואז הצהירו על המאפיין החדש. מקרה נפוץ הוא המרה של נתונים מ-API, שבהם מזהים ותאריכים מגיעים כמחרוזות:

Omit<ApiUser, "id" | "createdAt"> הוא { name: string }, ולכן id ו-createdAt החדשים לא מתנגשים בשום דבר. אפשר להרחיב באותה דרך גם את Pick, את Partial ואת שאר ה-utility types.

extends עם מחלקות

class Dog extends Animal הוא ירושת מחלקות של JavaScript, ובניגוד ל-extends של interface הוא קיים בזמן ריצה: Dog מקבל את הבנאי, את השדות ואת מימושי המתודות של Animal, ו-new Dog(...) instanceof Animal הוא true. TypeScript מוסיף מעל זה בדיקות טיפוסים, כמו מילת המפתח override:

מחלקה יכולה לעשות extends רק למחלקה אחת, אבל היא יכולה לעשות implements לכמה interfaces שרוצים. implements לא מעתיק שום דבר: הוא רק בודק שהמחלקה מצהירה על מה שה-interface דורש. super, איברים מוגנים ו-override מוסברים בעמוד על ירושה.

extends בגנריקס וב-conditional types

בתוך סוגריים משולשים, extends הוא אילוץ. <T extends { length: number }> מקבל כל ארגומנט טיפוס שיש לו length מספרי, ומאפשר לגוף הפונקציה להשתמש ב-.length. ב-conditional type, T extends U ? X : Y שואל האם T ניתן להשמה ל-U.

בשני המקומות extends אומר "ניתן להשמה ל", אותו יחס שצורת ה-interface בודקת. אילוצים מוסברים לעומק בעמוד על אילוצים גנריים, ובדיקות ברמת הטיפוסים בעמוד על conditional types.

שאלות נפוצות

איך מרחיבים interface ב-TypeScript?

כותבים interface Child extends Parent { ... }. לממשק הילד יש כל מאפיין של ההורה, ובנוסף המאפיינים שהוא מצהיר עליהם. כדי להרחיב כמה interfaces בבת אחת, מפרידים ביניהם בפסיקים: interface C extends A, B { ... }.

איך מרחיבים type alias ב-TypeScript?

type alias לא יכול להשתמש ב-extends; במקום זה משלבים טיפוסים עם intersection: type Admin = User & { permissions: string[] }. גם interface יכול להרחיב ישירות type alias של אובייקט: interface Admin extends User { permissions: string[] }.

אפשר לדרוס את הטיפוס של מאפיין כשמרחיבים interface?

רק בטיפוס תואם וצר יותר: kind: "circle" יכול להחליף את kind: string. טיפוס אחר, כמו number במקום מאפיין string, הוא השגיאה TS2430 (Interface 'X' incorrectly extends interface 'Y'). כדי להחליף מאפיין בטיפוס שאינו קשור, הסירו אותו קודם: interface User extends Omit<ApiUser, "id"> { id: number }.

מה ההבדל בין extends ל-implements?

extends יורש: interface מקבל את האיברים של ההורה, ומחלקה מקבלת את השדות ואת מימושי המתודות של מחלקת ההורה. implements רק בודק: מחלקה שמממשת interface חייבת להצהיר בעצמה על כל איבר, והיא לא מקבלת שום דבר מה-interface.

מה המשמעות של extends בגנרי כמו <T extends string>?

זה אילוץ, לא ירושה. T extends string אומר שארגומנט הטיפוס חייב להיות ניתן להשמה ל-string, ולכן בתוך הפונקציה אפשר להשתמש במתודות של מחרוזות על T. ב-conditional type, T extends U ? X : Y הוא בדיקה: אם T ניתן להשמה ל-U, התוצאה היא X, אחרת Y.

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

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

להתחיל