TypeScript - איך להשתמש בניבויי סוג כדי לצמצם סוג לסוג שלא ניתן לשיוך?
אני צריך לסנן מערך על בסיס מצב undefined של תכונה מסוימת בערכיו. כדי להשיג זאת, אני משתמש בפרדיקט טיפוס כדי לאכוף את סוג התוצאה של filter. הערכים במערך מכילים תכונה נדרשת, אשר היא אופציונלית בסוג של פרדיקט הטיפוס.
עם הדוגמה הבאה (TS Playground):
interface OptionalModel {
prop?: string;
otherProp: number;
}
interface RequiredModel {
prop: string | undefined;
otherProp: number | undefined;
}
const array: RequiredModel[] = [];
// How to filter/convert `array` to an array of `OptionalModel` ?
const filterKO = array.filter((value): value is OptionalModel => typeof value.otherProp !== 'undefined'); // ✗
יש שגיאה בהכרזת filterKO. TS אומר על OptionalModel במנבא סוג:
A type predicate's type must be assignable to its parameter's type.
Type 'OptionalModel' is not assignable to type 'RequiredModel'.
Property 'prop' is optional in type 'OptionalModel' but required in type 'RequiredModel'.(2677)
השימוש בפראדיקט סוג אינו אפשרי בגלל התכונה האופציונלית ב-OptionalModel; אך השימוש ב-array ישירות כ-OptionalModel[] גם אינו אפשרי בגלל האפשרות שהמאפיין האחר ב-RequiredModel יהיה undefined.
איך אני יכול להשיג את הסינון, כך שהסוג של filterKO יצומצם ל-OptionalModel[] ?
תשובה #1
TypeScript ברובו לא מדגם שינויים אקראיים של טיפוסים. השפה לא רוצה שתיקח ערך v מהסוג V ופשוט תשנה את הסוג שלו לסוג W, עבור V ו-W אקראיים.
במקום זאת, הוא מדגים שני סוגי שינויי טיפוסים בסיסיים. הראשון הוא הרחבה, שהיא (בקירוב ראשון) בטוחה. אם כל ערך מטיפוס V הוא גם ערך מטיפוס W, אז אפשר להקצות בבטחה V במקום שמצופה W. אין צורך במנחי טיפוס או במשהו אחר לכך; פשוט הקצה זאת למשתנה חדש שמסומן בטיפוס הרחב יותר:
השני הוא צמצום, שהוא בדרך כלל לא מוגן. אם כל ערך מסוג W הוא גם ערך מסוג V, אך אם לא כל ערך מסוג V הוא גם ערך מסוג W, אז אי אפשר פשוט להקצות V במקום שבו מצופה W. במקום זאת, כדי להיות בטוחים, יש לבצע סוג של שמירת סוג על V שלך כדי לוודא שהוא גם W. ל-TypeScript יש כמה אמצעים לעשות זאת, כולל תחבירי סוג המאפשרים לך לכתוב פונקציות שמירת סוג מותאמות אישית.
אבל אתה לא יכול להגיע מ-RequiredModel ל-OptionalModel ישירות דרך הרחבה או דרך צמצום. אף אחד מ-RequiredModel או OptionalModel אינו ניתן להקצאה לאחר. לכן אתה לא יכול להשתמש באף אחד מהגישות של הרחבה או צמצום מיד.
עם זאת, זה לא בלתי אפשרי. גם אם אף אחד מהסוגים V או W אינו תת-סוג של השני, ניתן תמיד להציג סוג שלישי ולהשתמש בו כמתווך. לדוגמה, סוג האיחוד V | W הוא הרחבה של גם V וגם W. כל ערך מסוג V ניתן להקצאה לסוג V | W. וכל ערך מסוג W גם הוא ניתן להקצאה לסוג V | W. כך ניתן להרחיב בחופשיות מ-V ל-V | W, ואז להשתמש בטכניקת הצמצום כדי לצמצם מ-V | W ל-W.
הנה איך זה נראה:
const array: RequiredModel[] = [];
const widenedArray:
(RequiredModel | OptionalModel)[] = array; // okay
const filtered = widenedArray.filter(
(value): value is OptionalModel => typeof value.otherProp !== 'undefined'
);
// const filtered: OptionalModel[] // okay
ראשית אנו מרחיבים את array מ-RequiredModel[] ל-(RequiredModel | OptionalModel)[] על ידי העתקת ההפניה שלו ל-widenedArray. ואז אנו מצמצמים את widenedArray מ-(RequiredModel | OptionalModel)[] ל-OptionalModel[] דרך פונקציית תחזית הסוג ל-filter().
קישור למזחלת לקוד
