Menu

טיפוסי אובייקט ב-TypeScript: מאפיינים אופציונליים ו-readonly

איך מגדירים טיפוסים לאובייקטים ב-TypeScript: טיפוסי אובייקט inline, מאפיינים אופציונליים עם ?, מאפייני readonly, אובייקטים מקוננים, מתודות, בדיקת מאפיינים עודפים, וההבדל בין object, {} ו-Object.

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

טיפוס אובייקט ב-TypeScript מפרט את המאפיינים שיש לאובייקט ואת הטיפוס של כל אחד מהם: { name: string; age: number }. הוסיפו ? כדי להפוך מאפיין לאופציונלי ו-readonly כדי למנוע השמה מחדש שלו. כתבו את הטיפוס inline, או תנו לו שם עם type או interface כדי להשתמש בו שוב.

כתיבת טיפוסי אובייקט

מפרידים בין מאפיינים עם ; או , (שניהם עובדים, ; הוא הסגנון המקובל), וגם ירידת שורה לבדה מספיקה. טיפוס inline מתאים לפרמטר חד פעמי; לכל דבר שמשתמשים בו פעמיים, תנו שם.

// Inline, in a parameter
function area(rect: { width: number; height: number }): number {
    return rect.width * rect.height;
}

// Named with a type alias
type Rect = { width: number; height: number };

// Named with an interface (the same shape)
interface RectShape {
    width: number;
    height: number;
}

type ו-interface מתארים צורות של אובייקטים באותה מידה. ההבדלים (מיזוג הצהרות, איחודים) מוסברים בדף interface מול type.

גישה למאפיין שהטיפוס לא מצהיר עליו היא שגיאת קומפילציה: point.z נותן TS2339, Property 'z' does not exist on type '{ x: number; y: number; }'.

מאפיינים אופציונליים

? אחרי השם מאפשר להשמיט את המאפיין. קריאה של מאפיין אופציונלי נותנת T | undefined, ולכן TypeScript מחייב אתכם לטפל במקרה החסר לפני השימוש בו.

קריאה למתודה על מאפיין אופציונלי בלי בדיקה היא שגיאה: p.nickname.toUpperCase() נכשל עם TS18048, 'p.nickname' is possibly 'undefined'. השתמשו ב-optional chaining (p.nickname?.toUpperCase()) כש-undefined הוא תוצאה מקובלת.

prop?: T ו-prop: T | undefined אינם אותו דבר. הראשון מתיר שהמפתח ייעדר; השני מחייב את המפתח, גם אם הערך שלו יכול להיות undefined:

מאפייני readonly

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

הפלט מראה את שתי המגבלות: ה-id באמת השתנה בזמן ריצה (רק הקומפיילר ידע שהוא readonly), והמערך שבפנים שונה. למערך לקריאה בלבד השתמשו ב-readonly string[]; כדי להפוך את כל המאפיינים ל-readonly בבת אחת, השתמשו ב-Readonly<Order>.

בדיקת מאפיינים עודפים

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

index.ts(8,8): error TS2561: Object literal may only specify known properties, but 'colour' does not exist in type 'Options'. Did you mean to write 'color'?

הקוד הוא TS2561 כי הקומפיילר מצא התאמה קרובה; מאפיין נוסף בלי שם דומה נותן TS2353, Object literal may only specify known properties, and 'z' does not exist in type 'Point'. בלי הבדיקה, שגיאת ההקלדה הייתה עוברת קומפילציה, color היה undefined, והתוכנית הייתה מציירת בשחור בלי שום התראה. הבדיקה חלה רק על ליטרלים חדשים. אובייקט שכבר נמצא במשתנה יכול לשאת מאפיינים נוספים, כי מערכת הטיפוסים של TypeScript מבנית: ערך מתאים לטיפוס כשיש לו לפחות את המאפיינים הנדרשים.

אובייקטים מקוננים ומתודות

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

לצורות עמוקות או כאלה שחוזרות, תנו שם לטיפוס הפנימי (type Address = { ... }) והפנו אליו, או חלצו אותו מהטיפוס החיצוני בגישה לפי אינדקס, Company["address"], כמו בשורות האחרונות.

object מול {} מול Object

שלושה טיפוסים נשמעים דומים ומשמעותם שונה:

טיפוסמקבלדוחה
objectכל ערך לא פרימיטיבי: {}, [], פונקציות, מופעי מחלקות5, "a", true, null, undefined
{}כל ערך חוץ מ-null ו-undefined, כולל פרימיטיבייםnull, undefined
Objectאותו דבר כמו {}, ובנוסף בדיקה שאיברים מובנים כמו toString שומרים על טיפוסים תואמיםnull, undefined
{ x: number }כל ערך עם x מספריערכים בלי x

{} לא אומר "אובייקט ריק"; הוא אומר "לא null ולא undefined". כדי לקבל כל אובייקט עם מפתחות לא ידועים, השתמשו ב-Record<string, unknown>; למיפוי של מפתחות לערכים, השתמשו ב-index signature או ב-Record, כמו שמוצג בדף dictionary. ברוב המקרים, צורה ספציפית עדיפה על כל אחד משלושתם.

שאלות נפוצות

איך מגדירים טיפוס אובייקט ב-TypeScript?

כותבים את המאפיינים ואת הטיפוסים שלהם בתוך סוגריים מסולסלים: { name: string; age: number }. אפשר לכתוב אותו inline בתוך הערת טיפוס, או לתת לו שם עם type User = { ... } או interface User { ... } ולהשתמש בו שוב. מפרידים בין המאפיינים עם ; או ,.

איך הופכים מאפיין לאופציונלי ב-TypeScript?

מוסיפים ? אחרי שם המאפיין: { name: string; nickname?: string }. האובייקט יכול לוותר על nickname, וקריאה שלו נותנת string | undefined, ולכן צריך לבדוק אותו או לתת ערך ברירת מחדל (user.nickname ?? user.name) לפני שמשתמשים בו כמחרוזת.

מה ההבדל בין prop?: string לבין prop: string | undefined?

עם prop?: string אפשר להשמיט את המפתח לגמרי. עם prop: string | undefined המפתח חובה, גם אם הערך שלו יכול להיות undefined, ולכן {} הוא שגיאת קומפילציה. קריאה של כל אחד מהם נותנת string | undefined.

מה ההבדל בין object, {} ו-Object ב-TypeScript?

object פירושו כל ערך שאינו פרימיטיבי (אובייקטים, מערכים, פונקציות), והוא דוחה את 5 או את "a". {} פירושו כל ערך חוץ מ-null ו-undefined, כולל פרימיטיביים. Object כמעט זהה ל-{}, אבל בודק גם שמתודות מובנות כמו toString שומרות על טיפוסים תואמים. השתמשו ב-object, או עדיף בצורה ספציפית, ולא ב-{} או ב-Object.

למה TypeScript מתלונן שליטרל אובייקט יכול לציין רק מאפיינים ידועים?

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

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

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

להתחיל