דרך פשוטה יותר לבדוק את סוג האובייקט הליטרלי ב-TypeScript
אני רוצה להשתמש ב-Object.assign על אובייקט מסוג ידוע, עם קבוצת תכונות מאובייקט ליטרלי שצריכות להיות מאותו סוג. האם יש דרך נקייה יותר לעשות זאת ב-TypeScript, מבלי לקרוא בפועל לפונקציית זהות או ליצור משתנה נפרד, כפי שמוצע בשאלה קשורה זו?
type Person = {
first?: string;
last?: string;
}
// Overkill, actually generates code to call a function
function check<T>(value: T): T { return value; }
const dude: Person = {
first: 'Mike',
};
Object.assign(dude, check<Person>({ // <- trying not to call a function
last: 'Johnson',
age: 27, // <-- should be flagged as unknown property
}));
תקציר - מחפש דרך לבדוק טיפוס של אובייקט מילולי ישירות.
תשובה #1
אני לא יכול לחשוב על פתרון "מושלם" לכך שעובד בכל מצב. רוב המנגנונים שמצליחים לשכנע את המעבד הקומפיילר לבצע את בדיקת הסוגים שאתה רוצה, מלוּוים גם בקוד ריצה נוסף (כגון הקצאת משתנה ביניים חדש, או קריאה לפונקציית זהות), שאתה אמרת שאינך רוצה.
בעיה אחת היא של-TypeScript אין אנוטציות סוגים מקוונות. (ראה microsoft/TypeScript#7481 ו-microsoft/TypeScript#13208 לדיון.) תרצה להיות מסוגל לבקש מהמחשב לבדוק (verify) שהביטוי {...} הוא מסוג Person, ולהתלונן אם לא ניתן לאמת זאת. האופרטור הקרוב ביותר שיש לנו ב-TypeScript הוא אקספרסיית סוג מהצורה {...} as Person. אבל זה אומר למחשב שהביטוי {...} הוא מסוג Person; אתה רוצה לשאול.
אפילו אם היה לנו אופרטור הערת שקוף בקו אחד, ישנה בעיה נוספת: טיפוסי אובייקט ב-TypeScript אינם מדויקים. (ראו microsoft/TypeScript#12936 לדיון.) טיפוסי אובייקט ב-TypeScript הם פתוחים בכך שניתן להוסיף להם עוד תכונות מבלי לשבור תאימות. אם יש לכם אובייקט מטיפוס Person, אתם יודעים משהו על התכונות first ו-last שלו, אך אינכם באמת יודעים כלום על שאר התכונות. רק כי ההגדרה של Person אינה מזכירה את תכונת age, זה לא אומר שאובייקט מטיפוס Person לא יכול להכיל תכונת age. ייתכן בהחלט שיש ממשק כזה:
interface AgedPerson extends Person {
age: number;
}
הטבע המבני של מערכת הטיפוסים ב-TypeScript אומר ש-{last: "Johnson", age: 27} הוא AgedPerson תקין גם אם אינך מכריז עליו ככזה (ואף אם AgedPerson לא מוגדר). וכיוון ש-AgedPerson הוא תת-סוג תקין של Person, אז {last: "Johnson", age: 27} הוא גם Person תקין.
עכשיו, כאשר אנשים משתמשים בליטרלים של אובייקטים כמו {last: "Johnson", age: 27}, בדרך כלל הם לא מתכוונים להוסיף תכונות נוספות כאלה, לכן ל-TypeScript יש תכונה שנקראת בדיקת תכונות עודפות (excess property checking) שמטפלת בליטרלים של אובייקטים כאילו הם בעלי טיפוסים מדויקים ומתלוננת אם אתה מוסיף תכונות לא ידועות. תכונה זו שימושית, אך מאוד קל לעקוף אותה. לכן חשוב לציין שאם תשנה את הקוד שלך במשהו, האזהרה לגבי age כתכונה עודפת עשויה להיעלם:
const ageDude = { last: 'Johnson', age: 27 };
const personDude: Person = ageDude; // no error
למרות זאת, עבור הדוגמה המסוימת שנתת, הפתרון שהייתי ממליץ עליו יהיה:
Object.assign<Person, Person>(dude, {
last: 'Johnson',
age: 27, // error
});
כאן אתה מציין באופן ידני את פרמטרי הסוג הכלליים בקריאה ל-Object.assign, כאשר פרמטר הסוג הראשון מתייחס לסוג של הארגומנט הראשון של הפונקציה, ופרמטר הסוג השני מתייחס לסוג של הארגומנט השני של הפונקציה. אתה רוצה שהקומפיילר יתייחס לשניהם כ-Person, אז עליך לכתוב Object.assign<Person, Person>(...). ושגיאת המאפיין העודף הצפוי מופיעה.
בסדר, מקווה שזה יעזור; בהצלחה!
קישור ל-Palyground לקוד
