Menu

TypeScript Omit: הסרת מאפיינים מטיפוס (דוגמאות)

Omit<T, K> יוצר טיפוס עם כל המאפיינים של T חוץ מהמפתחות K. למדו Omit עם מפתח אחד ועם כמה מפתחות, דריסת טיפוס של מאפיין, הסרת המאפיין בזמן ריצה, Omit מול Exclude ו-Pick, למה Omit מקבל מפתחות שלא קיימים, Omit קפדני, ו-Omit על טיפוסי איחוד.

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

Omit<T, K> הוא utility type מובנה שיוצר טיפוס אובייקט עם כל המאפיינים של T חוץ מהמפתחות שמפורטים ב-K. השימוש הקלאסי: טיפוס User שיש לו password, וגרסה ציבורית שאין לה.

PublicUser נשאר מסונכרן עם User: הוסיפו שדה ל-User והוא יופיע גם ב-PublicUser, בזמן ש-password נשאר בחוץ. Omit עובד על טיפוסים בלבד; ה-destructuring ב-toPublic הוא מה שמסיר את המאפיין מהאובייקט עצמו.

השמטת כמה מפתחות

הארגומנט השני הוא איחוד, אז כתבו כמה מפתחות שצריך עם |:

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

איך Omit מוגדר

Omit בנוי משני utility types אחרים:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

קראו אותו מבפנים החוצה: keyof T הוא האיחוד של כל המפתחות, Exclude<keyof T, K> מסיר מהאיחוד את אלה שלא רוצים, ו-Pick שומר את המפתחות שנשארו. כך ש-Omit<User, "password"> הוא Pick<User, "id" | "name" | "email">. מכיוון ש-Pick ממפה על המפתחות של T, המאפיינים שנשארו שומרים על המגדירים readonly ו-?.

שימו לב להגבלה: K extends keyof any, ולא K extends keyof T. הפרט הזה הוא הנושא של הקטע הבא.

Omit לא בודק שהמפתח קיים

keyof any הוא string | number | symbol, ולכן Omit מקבל כל מפתח שהוא, כולל מפתח שאין ל-T. שגיאת הקלדה עוברת קומפילציה ולא מסירה כלום:

אין שגיאה בשום מקום, והסיסמה יוצאת ב-JSON. ההגבלה הרופפת נבחרה בכוונה כש-Omit נוסף ב-TypeScript 3.5. הסיבות של הצוות: בקוד גנרי המפתחות המושמטים לרוב לא מגיעים מ-T (פונקציה שממזגת שני אובייקטים יכולה לתת לארגומנט ה-rest שלה את הטיפוס Omit<T1, keyof T2>), גם Exclude לא מגביל את הפרמטר השני שלו, וגרסה מוגבלת הייתה שוברת הרבה מטיפוסי ה-Omit שפרויקטים כבר כתבו בעצמם. לקוד יומיומי, גרסה קפדנית יותר תופסת את שגיאת ההקלדה:

index.ts(10,36): error TS2344: Type '"pasword"' does not satisfy the constraint 'keyof User'.
index.ts(11,7): error TS2741: Property 'password' is missing in type '{ id: number; name: string; }' but required in type 'PublicUser'.

השגיאה הראשונה היא החשובה: המפתח נבדק מול keyof User. השנייה נובעת ממנה, כי שום דבר לא הושמט ו-PublicUser עדיין דורש password. תקנו את האיות ל-"password" והקוד יעבור קומפילציה. בסיסי קוד רבים מגדירים אותו פעם אחת בקובץ טיפוסים משותף ומשתמשים בו במקום ב-Omit.

דריסת טיפוס של מאפיין

interface לא יכול להרחיב interface אחר ולשנות מאפיין לטיפוס לא תואם: interface ApiUser extends User { id: string } נכשל עם error TS2430: Interface 'ApiUser' incorrectly extends interface 'User'. השמיטו קודם את המאפיין, ואז הצהירו עליו שוב:

עם type alias אותו דבר נעשה בחיתוך: type ApiUser = Omit<User, "id"> & { id: string }. בלי ה-Omit, החיתוך User & { id: string } היה נותן ל-id את הטיפוס number & string, שהוא never.

Omit על טיפוסי איחוד

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

index.ts(8,39): error TS2353: Object literal may only specify known properties, and 'radius' does not exist in type 'NewShape'.

NewShape הפך ל-{ kind: "circle" | "square" }. כדי להשמיט מכל איבר בנפרד, הפעילו את Omit בתוך טיפוס מותנה דיסטריבוטיבי:

T extends unknown ? ... : never תמיד מתקיים; הוא שם רק כדי לגרום לטיפוס המותנה להתפלג על האיחוד. דף הטיפוסים המותנים מסביר למה זה עובד. השתמשו ב-DistributiveOmit בכל פעם שהטיפוס שממנו משמיטים הוא discriminated union.

השמטת מאפיין מקונן

Omit מסתכל רק על המפתחות ברמה העליונה, ולכן Omit<Settings, "editor.wordWrap"> לא מסיר כלום (וכמו שראינו למעלה, גם לא מתלונן). כדי להוריד מאפיין רמה אחת פנימה, השמיטו את ההורה והוסיפו אותו בחזרה עם Omit משלו:

Settings["editor"] הוא indexed access type: הוא נותן שם לטיפוס האובייקט המקונן כדי ש-Omit יוכל לעבוד עליו.

הסרת מאפיינים בזמן ריצה

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

פונקציית העזר מגבילה את K ל-keyof T, ולכן מפתח עם שגיאת כתיב הוא שגיאה במקום הקריאה. ההעתקה רדודה: אובייקטים מקוננים משותפים עם המקור.

Omit מול Pick מול Exclude

עובד עלשומרדוגמהתוצאה
Omit<T, K>טיפוסי אובייקטכל מאפיין חוץ מ-KOmit<User, "password">{ id; name; email }
Pick<T, K>טיפוסי אובייקטרק את המאפיינים ב-KPick<User, "id" | "name">{ id; name }
Exclude<U, M>טיפוסי איחודאיברי איחוד שלא ניתנים לשיוך ל-MExclude<"a" | "b" | "c", "a">"b" | "c"

בחרו בין Omit ל-Pick לפי מה שצריך לקרות כשטיפוס המקור גדל. Omit כולל מאפיינים חדשים אוטומטית, וזה מתאים ל"הכול חוץ מהסוד". Pick אף פעם לא כולל אותם, וזה מתאים ל"בדיוק השדות האלה" ובטוח יותר לכל דבר שיוצא מהשרת שלכם. Pick בודק את המפתחות שלו מול keyof T; Omit לא.

שאלות נפוצות

מה Omit עושה ב-TypeScript?

Omit<T, K> בונה טיפוס אובייקט חדש עם כל המאפיינים של T חוץ מאלה שהמפתחות שלהם נמצאים ב-K. Omit<User, "password"> הוא User בלי password. הוא משנה רק את הטיפוס; הוא לא מסיר שום דבר מאובייקט בזמן ריצה.

איך משמיטים כמה מאפיינים ב-TypeScript?

מעבירים איחוד של מפתחות כארגומנט השני: Omit<User, "password" | "createdAt" | "updatedAt">. כל מפתח באיחוד מוסר.

מה ההבדל בין Omit ל-Exclude?

Omit מסיר מאפיינים מטיפוס אובייקט: Omit<{ a: 1; b: 2 }, "a"> הוא { b: 2 }. Exclude מסיר איברים מטיפוס איחוד: Exclude<"a" | "b", "a"> הוא "b". Omit ממומש בעזרת Exclude: Pick<T, Exclude<keyof T, K>>.

למה Omit לא נותן שגיאה על מפתח שלא קיים?

פרמטר המפתח שלו מוגבל ל-keyof any (כל מחרוזת, מספר או symbol), ולא ל-keyof T, ולכן שגיאת הקלדה כמו Omit<User, "pasword"> עוברת קומפילציה ולא מסירה כלום. הגדירו type StrictOmit<T, K extends keyof T> = Omit<T, K> כדי לקבל שגיאה על מפתחות לא מוכרים.

איך מסירים מאפיין מאובייקט בזמן ריצה ב-TypeScript?

השתמשו ב-destructuring עם איבר rest: const { password, ...rest } = user;. TypeScript נותן ל-rest את טיפוס האובייקט בלי password, אותה צורה כמו Omit<User, "password">, והמאפיין באמת לא קיים באובייקט החדש.

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

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

להתחיל