צלילה לעומק TS: טייפ X לא זהה ל-union של כל הערכים האפשריים שהטייפ כולל
- 
בפוסט זה אני הולך לשתף דבר שלמדתי לאחרונה ב-TS שהיה חידוש עבורי 
 אני משתמש בנושא זה כקרש קפיצה לכתוב מבוא כללי ל-TS. כדי להסביר את החידוש שגיליתי, אסביר בצורה מתומצתת את המושגים שצריכים להכיר כדי להבין את החידוש
 (אם מישהו יתחיל להשתמש בשפה בזכותי והיה זה שכרי. פשוט חבל שיש כל כך הרבה מתכנתים מוכשרים בקהילה שלנו שלא משתמשים ב-TS מחוסר היכרות...)מכיון שאני כותב את הפוסט בהמשכים אשמח אם התגובות יהיו בנושא נפרד התוכנית: - קורס מזורז ב-TS
- קטעי הקוד המוקשים לכאורה
- התשובה
 מקווה שיהיה קל לקריאה ולתועלת ובכן, אעביר אותך קורס מזורז מאוד ב-TS: מה זה טייפדרך אחת להסתכל על טייפ הוא ככה: 
  טייפ הוא סט (set - כן, ההוא מתורת הקבוצות...) של ערכים. טייפ הוא סט (set - כן, ההוא מתורת הקבוצות...) של ערכים.
 לדוגמה:- הטייפ numberמייצג את הסט שכולל בתוכו את כל המספרים
- הטייפ 1מייצג סט שכולל בתוכו ערך אחד: המספר 1, כמו"כ כל ערך ב-JS הוא גם שם של טייפ שכולל חבר אחד - הערך הזה בעצמו
- הטייפ { foo: string; bar: number }מייצג את הסט שמכיל כל האובייקטים האפשריים שמכילים שדהfooמסוגstringושדהbarמסוגnumber
- הטייפ neverמייצג סט ריק. סט בלי חברים.
- הטייפ unknownמייצג את הסט של כל הערכים האפשריים. "סט סטי הסטים". הסט הכי גדול ביקום הטייפים. לכן אין הרבה מה לעשות עם ערך מסוג unknown כי אתה לא יודע איזה פעולות מותרים לעשות איתו.
 דרך נוספת לחשוב על טייפים: 
  הטייפ של הערך אומר לנו איזה פעולות מותרות על הערך הטייפ של הערך אומר לנו איזה פעולות מותרות על הערך
 למשל:- הטייפ numberאומר לנו שמותר לעשות פעולת חיבור וחיסור על ערך זה עם מספרים אחרים
- הטייפ stringאומר לנו שמותר לנו לקרוא לפונקציה בשםstartsWithעל הערך שלנו
 וכן הלאה
 זו גם דרך נכונה לחשוב על טייפים. 
 בהמשך המאמר נתמקד יותר בפן הראשון: טייפ של ערך מייצג חברות בסט של ערכים.מתמטיקה עם טייפים:TS נותן שני כלים אריתמטיים לבניית טייפים מורכבים מטייפים אחרים: חיבור - UNIONהאופרטור |ב-TS עושה את פעולת החיבור באריתמטיקה של טייפים
 למשל:- טייפ 1 | 2מייצג חיבור של שני הסטים1ו-2. התוצאה היא סט (טייפ) שכולל שני חברים, 1 ו-2
- string | numberמחבר את שני הסטים והתוצאה היא סט גדול שכולל בתוכו כל המחרוזות האפשריים וכל המספרים האפשריים
- חיבור עם never(להזכירכם: זהו הסט עם 0 חברים) הוא כמו פעולת חיבור מספרי של מספר כלשהו עם 0. הפעולה לא משנה את הערך המקורי.
 חיסור - INTERSECTIONלמדנו איך להרחיב סט עם אופרטור |עכשיו נלמוד איך להצר סט עם אופרטור&.
 האופרטור&מקבל שני סטים משני צידי האופרטור ומחזיר את הסט שכולל רק את החברים בשני הסטים גם יחד
 למשל:- { foo: string } & { bar:number }לוקח את הסט שכולל כל האובייקטים האפשריים שיש להם שדה- fooמסוג string, וכן את הסט שכולל כל האובייקטים האפשריים שיש להם שדה- barמסוג number, והוא מחזיר סט של כל האובייקטים האפשריים שחברים בשתי הקבוצות: דהיינו אובייקטים שיש להם גם שדה foo וגם שדה bar
- פעולת & unknownלא עושה כלום כי כל ערך אפשרי נמצא בתוך סט ה-unknown
 תכנות עם טייפיםTS נותן כלים יותר משוכללים לבניית טייפים על ידי "תכנות" פונקציות - GENERICSתחביר ה-generics מקביל לפונקציה המוכרת מתכנות רגיל. 
 אבל בעוד שפונציקה רגילה פועלת על ערכים, ה-generic פועל על טייפים.
 זה פונקציה שמקבל כארגומנט טייפ, ופולט את הטייפ הסופי לפי הלוגיקה של ה-body של הפונקציה.
 לדוגמה:- פונקציה שמקבל נוסח תפילה ופולט את הטייפ של היהודי עם נוסח תפילה זו:
 // הצהרת טייפ שכולל כל נוסחאות התפילה האפשריים type Nusach = 'Ashkenaz' | 'Sefarad' | 'Edot Hamizrach' // הצהרת טייפ גנרי שמקבל נוסח תפילה ומחזיר טייפ של יהודי שמתפלל בנוסח הנ"ל type Jew<T extends Nusach> = { name: string nusach: T } // ערך עם שדה שם ושדה נוסח const aJew = { name: 'Zundel', nusach: 'Ashkenaz' } // נסיון השמה למשתנה מסוג Jew<'Sefarad'> // TS מתריע על אי התאמה בין הטייפ לערך const test: Jew<'Sefarad'> = aJewהשורה האחרונה תפלוט שגיאה, כי טייפ מסוג Jew<'Sefarad'>לא יכול להתפלל נוסח אשכנזמילת המפתחextendsבדוגמה דלעיל נתקלנו לראשונה (בקורס הזה) במילת המפתח extends, ב-TS הכוונה שלextendsהוא (לפי צורת החשיבה שהצגנו בתחילת דברינו) שהטייפ שבצד שמאלי כולו חבר בסט שמיוצג על ידי הצד הימני.
 ובדוגמה הנ"ל:T extends Nusachהכוונה הוא שהפרמטרTרק מקבל טייפים שהם חברים בסטNusach
 אם ננסה ליצור יהודי כזה:Jew<'foo'>נקבל שגיאה שהמחרוזתfooהוא לא חבר בקבוצתNusachנפגוש שוב את המילה extendsבהמשךתנאיםהתחביר לכתיבת תנאי ב-TS: T extends U ? V : Wשימו לב לנקודות הבאות: 
 בתכנות רגיל אנחנו מורגלים לבדיקת המון דברים בתנאים, וכן לשלוט על פעולות מגוונות על ידי תנאים אלו
 ב-TS לעומת זאת:- הפרט היחיד שאפשר לבדוק בתנאי הוא extends - כלומר, חברות של הצד השמאלי בצד הימני
- הדבר היחיד שאתה יכול לתלות בתנאי הזה הוא הטייפ של התוצאה של הביטוי
 דוגמה לשימוש בתנאי ופונקציה ביחד: type LabelOf<T> = T extends { label: string } ? T['label'] : 'no name'Index Signaturesאפשר לתאר את ה-shape של אובייקט בלי לפרט בדיוק את השם של השדות, במקום זה כותבים את הטייפ של המפתחות והטייפ של הערכים 
 לדוגמה:type X = { [key: string]: number }הכוונה בהצהרה זו הוא שמדובר באובייקט שיש בו מפתחות מסוג string וערכים מסוג number לולאות - Mapped TypesTS לוקח את התחביר הזה שלב נוסף קדימה באמצעות mapped types 
 בתחביר זה אפשר לעבור בלולאה על כל הערכים האפשריים הכלולים במפתח (key) ולקבוע את הטייפ של הערך עבור אותו מפתח לפי כל לוגיקה שתבחר
 דוגמה:type MyMap<T> = { [K in keyof T]: K extends `${string | undefined}foo${number}` ? string : number } type Test = MyMap<{ foo1: any, a_foo2: any, bar3: any }>keyofכאן אנחנו נתקלים לראשונה במילת המפתח keyof:
 זה אופרטור שמחזיר union של כל שמות השדות של האופרנד שלו
 למשל:keyof {foo:any, bar:any}שווה ל-'foo' | 'bar'ה"פונקציה" (יותר מקובל לקרוא לו generic type) MyMapמקבל פרמטרT(מכיון שלא הגבלנו אותו על ידי מילת המפתחextends- הוא יכול להיות מכל סוג שהוא), ומחזיר אובייקט שיש לו אותם שדות של האובייקט המקורי, אבל אם התבנית של שם השדה מתאים לתבנית מחרוזת אופציונלית + foo + מספרי כלשהו, אז הערך של השדה חייב להיות string, אחרת הערך חייב להיות numberיותר מקובל ושימושי להשתמש ב-mapped types עם תנאי שבודק את הטייפ של ה_ערך_ של האובייקט המקורי במקום תנאי שבודק את הטייפ של ה_מפתח_ של האובייקט המקורי. עושים את זה על ידי אינדוקס ככה: type MyMap<T> = { [K in keyof T]: T[K] extends X ? Y : Z }נראה דוגמה שימושית לזה מיד בקטע הבא דוגמה אחרונה:Omitבוא נבדוק את ההבנה שלך על ידי הדוגמה הבאה. 
 (ההגדרותExcludeו-Omitמגיעים מובנים ב-TS)// type Omit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P]; } // type Exclude<T, U> = T extends U ? never : T type MyObject = { id: number foo: string bar: number } type MyObjectWithoutId = Omit<MyObject, 'id'>טוב, התעייפתי לבינתיים, אמשיך בעז"ה בהזדמנות הבאה... 
- 
בפוסט הזה אולי לא נתקדם הלאה כי אני רוצה להבהיר כמה נקודות ולחזור על חלק מהמושגים מהפוסט הקודם הודעותא. תודה לכל מי שטרח להשאיר לי משוב, אם בצורת הצבעה על הפוסט ואם בערוצים פרטיים (כמה חברים כתבו אלי בערוצים אחרים), אני מעריך את זה! 
 ב. לכל מי שהתקשה להבין את הפוסט הקודם:
 אל תאשים את עצמך. זה לא היה פוסט קל להבנה! כתבתי דברים בקיצור, ולפעמים השמטתי לגמרי הסברים נחוצים.
 הנושא עצמו מאוד תיאורטי, וגם הדוגמאות לא היו ממש מעשיות.
 אני מתנצל. (אנסה להבא להביא דוגמאות יותר מעשיות)
 ולמי שלא התקשה כלל: או שיש לך הכירות קודמת עם הנושא, או שאתה מוכשר, או שלא הבנת באמת
 ג. חשוב להבהיר! אין שום צורך להכיר את כל התוכן התיאורטי שכתבתי כדי לקבל תועלת מ-TS. גם אם הנושא לא מושך אותך או אפילו דוחה אותך, אני עדיין ממליץ בחום להשתמש ב-TS כי אפשר לקבל רוב התועלת בלי להבין את הרקע התיאורטי. הפוסט שלי חשוב למי שרוצה ליצור טייפים. רוב הזמן אתה צורך טייפים שאחרים כתבו ולא יוצר טייפים חדשים. וגם רוב הטייפים שתיצור יהיו פשוטים בלי צורך בידע תיאורטי עמוק.
 ד. העירו לי על כמה דברים שלא הסברתי די הצורך בפוסט הקודם ואנסה להבהיר אותם כאןמה זה טייפ?(הרחבה של הקטע "מה זה טייפ" בפוסט הקודם)התפקיד של טייפים בשפות תכנות זהה לכל השפות: בהנתן ערך כלשהו, המתכנת רוצה לדעת בצורה סטטית (כלומר, בלי להריץ את התוכנה בפועל) איזה פעולות זמינים לו על ערך זה, איך מותר להשתמש בערך. 
 אבל יש הבדל מהותי בין ההתעסקות עם טייפים בשפות סטטיות (statically typed) "קלאסיות" (JAVA, C++, C#, etc) לטייפים ב-TS
 כי בשפות קלאסיות אין עיסוק ישיר עם טייפים עצמם. עוסקים עם קלאסים, שהם "תבניות" או "אב טיפוס" לערכים, קלאס מגדיר איזה שדות הערך חייב להכיל, ובאיזה פעולות הוא צריך לתמוך. וכתוצאה נוצר "טייפ" (שהוא הסט של כל הערכים האפשריים שמתאימים לתבנית) אבל אין עיסוק ישיר עם טייפים עצמם
 ב-TS יש עיסוק ישיר עם טייפים עצמם. מחברים טייפים אחד לשני, מחסרים טייפים אחד מהשני, בונים טייפים מטייפים אחרים, דברים שלא קיימים בשפות קלאסיות.
 בשפה קלאסית אין מושג של literal type (הטייפ של ערך literal, סט בעל ערך יחיד). כי כל טייפ הוא תוצאה מהצהרה של קלאס שהוא תבנית להרבה ערכים כי העיקר של הטייפ הוא הפעולות שזמינים עליו.
 לא כן ב-TS שאפשר לעסוק ישירות עם המושג טייפ.
 לכן חשוב לחשוב על טייפים ב-TS כסטים, במקום כתבניות כמו בשפות קלאסיותGENERICSהדוגמה שהבאתי בפוסט הקודם לא היה מספיק ברור וגם לא כל כך מעשי 
 אביא דוגמה מעשי ביותר
 נגיד שאתה בונה קליינט ל-API
 אתה יודע שה-API המדובר תמיד מחזיר תשובות בתבנית זה:type ApiResponse = { success: boolean data: object | undefined error: string | undefined }השימוש ככה: type User = { ... } type Transaction = { ... } class ApiClient { // users getUserById(id: number): ApiResponse { ... } updateUser(newUser: User): ApiResponse { ... } // transactions listTransactions(userId: number): ApiResponse { ... } // ... } const apiClient = new ApiClient const res: ApiResponse = apiClient.getUserById(id) if (res.success) { const user = res.data // do something with user } else { console.error(error) }הבעיה בקוד הזה הוא שבשורה 18, הדבר היחיד ש-TS יודע על המשתנה userהוא שזה מסוגobjectאבל הוא לא יודע איזה סוג אובייקט.
 דרך אחת לטפל בזה הוא להצהיר על טייפ בשםUserApiResponseושם להצהיר על הטייפ המלא של שדהdata
 הבעיה היא שצריך לחזור שוב ושוב על הצהרה זו לכל סוג שלXXXApiResponse
 אפשרות אחרת הוא להצהיר שהטייפ שלres.dataהואUserconst user = res.data as Userאבל בצורה זו מאבדים את הבטחת האבטחה של TS וצריך לסמוך על המתכנת שהצהיר נכון על הטייפ 
 הפתרון הוא להשתמש ב-generic שהוא "טייפ בונה טייפים"
 ככה:type ApiResponse<T> = { success: boolean data: T | undefined error: string | undefined } type User = { ... } type Transaction = { ... } class ApiClient { // users getUserById(id: number): ApiResponse<User> { ... } updateUser(newUser: User): ApiResponse<User> { ... } // transactions listTransactions(userId: number): ApiResponse<Transaction[]> { ... } // ... } const apiClient = new ApiClient const res: ApiResponse = apiClient.getUserById(id) if (res.success) { const user = res.data // user is type User } else { console.error(error) }בפסאודו קוד ההצהרה דומה לקוד כזה: const ApiResponse = function(T: type): type { return new Type(`{ success: boolean data: ${T} | undefined error: string | undefined }`) }תנאיםיש כמה בעיות בדוגמה שהבאתי למעלה עבור שימוש בתנאי 
 א. זה לא כל כך מעשי
 ב. יש באג (הטייפ לא עושה מה שהתכוונתי שהוא יעשה - אולי אכתוב על זה בפוסט אחר) ואין לי ממש דרך לפתור את הבאג
 ג. השתמשתי בתחביר שלא הצגתי מקודם (אינדוקס)אני רוצה להביא דוגמה מעשית פשוטה שבונה על הדוגמה הקודמת נגיד שה-API מחזיר לפעמים אובייקט יחיד ולפעמים מערך 
 אם הוא מחזיר מערך אז יש שדות נוספות בתשובה
 אולי נגיד שדהcountשמחזיר כמה אובייקטים יש בסה"כ
 איך נגדיר type כזה?type BaseApiResponse<T> = { success: boolean data: T | undefined error: string | undefined } type ApiResponse<T> = T extends any[] ? BaseApiResponse<T> & { count: number } : BaseApiResponse<T>אפשר לתרגם את זה לקוד פסאודו זה: function ApiResponse(T: type): type { const base = `{ success: boolean data: ${T} | undefined error: string | undefined }` if (T instanceof Array(any)) { return `base & { count: number }` } else { return base } }Mapped Typesהבאנו למעלה דוגמה שהיא גם מסובכת מדי וגם לא כל כך מעשי לשימוש יומיומי 
 אני רוצה להביא דוגמה נורמלית מתוך הטייפים המובנים (TS מגיע עם "ספרייה סטנדרטית" של טייפים מובנים)
 נבנה על הדוגמה הקודמת שלנו של קליינט API
 אז אתה רוצה להגדיר שהפונקציהupdateUserמקבל אובייקט מסוגUserאבל בשונה מ-User, כל השדות הם אופציונליות. (כי מי שרוצה לעדכן רק שדה אחת לא צריך לשלוח את כל השדות)אז אפשר לכתוב טייפ חדש type User = { name: string foo: string bar: number } type UserUpdate = { name?: string foo?: string bar?: number }אבל זה בזבוז נייר דיו וביטים, וכמובן זה לא DRY מה שאמור לזעזע כל מתכנת ששוה משהו הפתרון הוא: updateUser(newUser: Partial<User>): ApiResponse<User> { ... }השתמשנו ב-helper (כך נקראים "פקונציות" אלו שעוזרים לבניית טייפים מבוססים טייפים קיימים) בשם Partialשלוקח טייפ ומחזיר טייפ עם אותם שדות, רק שכולם נהיים אופציונליים
 איך עובד ה-helper הזה?
 נסתכל בקוד המקור:type Partial<T> = { [P in keyof T]?: T[P] | undefined; }מילת המפתח inעובד ככה: עבור כל חבר ב-union שצד הימני מייצג, הוא מריץ את הביטוי שאחרי הנקדותיים כאשר המשתנהPמייצג את החבר הזה מה-unionשימו לב לתחביר שלא הצגנו מקודם: T[P]- זה אינדוקס בטייפ שעובד בדיוק כמו אינדוקס אובייקט רגיל.T[P]הוא הטייפ של שדהPבתוך טייפTנתרגם את זה לפסאודו קוד: function keyOf(o: object): union { return new Union(Object.keys(o)) } function Partial(T: type): type { const res = {} for (const P in keyOf(T)) { res[P] = T[P] | undefined } return res }(סליחה. בפסאודו קוד הזה השתמשתי במוסכמות אחרות מהקטעים הקודמים. בקודמים יצרתי טייפ על ידי העברת מחרוזת ל"בנאי של הטייפ" - כביכול. הפעם לשם נוחות התייחסתי לטייפ כאובייקט. זה רק פסאודו קוד כמובן ואני מקווה שהכוונה ברורה) 
- 
הטייפ השולל:neverנחזור שוב ל- neverלכמה רגעים
 הטייפneverהוא סט בלי חברים, טייפ ששום ערך לא יכול להיות בו חבר.
 נמצא שלא יקרה אף פעם שלערך אמיתי יהיה טייפnever
 אם כן לְמָה זה טייפ שימושי?שלילת מצבאחד מהשימושים של טייפ זה הוא לסמן ל-TS שמצב מסויים אמור להיות בלתי אפשרי, ולקבל אישור מ-TS שזה באמת בלתי אפשרי 
 למשל: אם רוצים לוודא שב-switch עברנו על כל האפשרויות ולא פיספסנו אחד מהם בטעות אפשר לכתוב ככה:type Color = 'red' | 'blue' | 'green' function handleColor(color: Color) { switch (color) { case 'red': // ... break case 'blue': // ... break case 'green': // ... break default: throw new Error('Impossible!') } }פה אנחנו בודקים בזמן ריצה שלא הגענו לענף הבלתי אפשרי אבל עם TS יש לנו אפשרות לבדוק בזמן קימפול שלא שכחנו לטפל בשום אפשרות 
 מוסיפים שורה זו:type Color = 'red' | 'blue' | 'green' function handleColor(color: Color) { switch (color) { case 'red': // ... break case 'blue': // ... break case 'green': // ... break default: const _test:never = color throw new Error('Impossible!') } }עיין שורה 15. אנחנו מצהירים ל-TS שבשלב הזה מיצינו את כל הערכים האפשריים ש-color יכול לקבל ופה הטייפ חייב להיות never, כלומר זה לא יקרה אף פעם 
 (להבין יותר איך זה עובד, כדאי להכיר את המושג narrowing, בקיצור נמרץ: מנוע TS עוקב אחרי משתנים תוך כדי זרימת הקוד וכאשר הוא רואה ביטויים מסויימים בקוד, הוא מבין אותם ומוציא מתוך ה-union של הטייפ של המשתנים את אותם הטייפים שהקוד שולל, ובהקשר שלנו, בכל ענף של ה-switch, מנוע TS יכיר בעובדה שהטייפ שלcolorהוא רק הערך שאותו ענף מטפל, ובענףdefaultהטייפ הוא האפשרויות שה-switch לא מיצה)
 ברגע שנוסיף עוד אפשרות ל-union שלColorנקבל מיד אזהרה שהמשתנה_testקיבל ערך שהטייפ שלו הוא לאneverשלילת ערךאבל השימוש שמעניין אותנו יותר פה הוא השימוש השני, 
 כאשר יוצרים טייפ חדש באמצעות ביטוי עם תנאי, ורוצים שענף אחד של התנאי לא יחזיר שום טייפ, או יותר נכון: יחזיר טייפ ריק, במקום כזה באneverלידי שימוש
 נדגים את זה על ידי ה-helper המובנה -Exclude
 (helper הוא פונקציה שעוזר לבניית טייפ שמבוסס על טייפ קיים. נזכיר: פונקציה ב-TS == generic)
 הטייפExcludeמגיע מובנה ב-TS ושימושו הוא כאשר רוצים ליצור מ-union קיים, טייפ שדומה למקור רק שחסר ממנו אחד או יותר ערכים.
 למשל:type Weekday = 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' type Workday = Exclude<Weekday, 'friday' | 'saturday'>כפי שאפשר לראות: ה"פונקציה" Excludeמקבלת כפרמטר ראשון את הטייפ המקורי ובפרמטר השני union של טייפים (או טייפ יחיד) לחסר מהטייפ המקורי.איך הוא עובד? 
 הנה קוד המקורtype Exclude<T, U> = T extends U ? never : Tלהבין את זה צריך להקדים עוד דבר אחד: ביטוי של תנאי שמריצים על union עובד על כל אבר של ה-union בנפרד 
 בתיעוד של TS קוראים לזה: Distributive Conditional Types
 כלומר זה עובד כמו הקוד פסואודו הבא:function Conditional(source: Union, condition: Type => Bool, resultIfTrue: Type => Type, resultIfFalse: Type => Type) { const runConditional = (type: Type) => { if (condition(type)) { return resultIfTrue(type) } else { return resultIfFalse(type) } } return source.split('|').map(runConditional).join('|') } function Exclude(source: Union, exclusions: Union) { return Conditional(source, t => t.extends(exclusions), t => t, t => never) } Exclude(Weekday, 'friday' | 'saturday') // 'sunday' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | never | neverמקווה שהפסאודו קוד מובן 
 רק מזכיר שוב כמה נקודות שכבר הזכרנו:- בדיקת extends בודק עם הצד שמאלי כולו כלול בתוך הסט שהצד הימני מייצג
- פעולת union עם never - כמוהו כמו אי-פעולה. הוספת סט ריק לסט המקורי לא מוסיף שום דבר, לכן בתוצאה של Excludeאפשר להשמיט את ה-neverבלי שום השלכה
 הערה בענין מילתextends בדרך אגב, כמה אנשים שאלו שאלה זו: למה המצב שבו טייפ אחד כלול לגמרי בתוך טייפ אחר נקרא בלע"ז בדרך אגב, כמה אנשים שאלו שאלה זו: למה המצב שבו טייפ אחד כלול לגמרי בתוך טייפ אחר נקרא בלע"זextends, הרי המשמעות של extend בלע"ז הוא "הרחבה" ופה מדובר על הצרה?!
 התשובה לזה לכאורה (וכן הסכים לזה מתכנת אחר שדיבר איתי על זה) שהמילה "הרחבה" בהקשר זה לא מדבר על הסט שהוא תוצאה מהגדרת הטייפ אלא על הגדרת הטייפ עצמו,
 ככל שאתה יותר מאריך בהגדרת הטייפ, כך אתה שולל יותר ויותר מועמדים פוטנציאליים.
 לדוגמה: טייפunknownהוא הטייפ הכי גדול מצד אחד, כי הוא כולל כל הערכים האפשריים, ומצד שני הגדרתו הוא הכי קצר: אין שום הגדרה, כל מה שזז נכנס לתוך הטייפ הזה. ברגע שמרחיבים את ההגדרה, למשל מוסיפים תנאי שיש לו מתודה פלונית, אתה מצר את מעגל החברים בטייפ"פונקציית"Omitעכשיו נוכל להסביר את הטייפ Omitשבו סיימנו את הפוסט הראשון, וזה אמור להיות מובן בקלות כעתהפונקציה מקבלת בפרמטר הראשונה אובייקט, ובשנייה, union של מפתחות שאתה רוצה להשמיט ממנו 
 דוגמה לשימוש (מתוך התיעוד הרשמי):type Todo = { title: string description: string completed: boolean createdAt: number } type TodoPreview = Omit<Todo, "description">; שיניתי קצת מהמקור בתיעוד של TS ממילת המפתח שיניתי קצת מהמקור בתיעוד של TS ממילת המפתחinterfaceלמילתtypeרק לשם העקביות כי לא הצגנוinterfaceבמאמר זה - יש הבדל קטן בין השניים אבל זה נושא לשיחה אחרת ולא חשוב כעת
 פה אני רוצה להתמקד על האינטואיציות המרכזיות ולא על פרטים קטניםוהנה קוד המקור של Omittype Omit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P]; }ננתח את זה לחלקים: הפרמטריםOmit<T, K extends keyof any>הפונקציה מקבלת שני פרמטרים, הראשון ברור. זה יכול להיות כל טייפ שהוא. השני מוגבל לקבל רק טייפ שמקיים בעצמו התנאי extends keyof any
 מה זהkeyof any? זו דרך קצרה להגיד כל טייפ שחוקי להשתמש בו כמפתח של אובייקט כלשהו, שזה כהיום:string | number | symbol. אז הפרמטר השני הוא טייפ שמורכב רק מדברים שמותרים במפתח אובייקטה-body של הפונקציה{ [P in Exclude<keyof T, K>]: T[P]; }יש פה שימוש בכמה דברים שהסברנו כבר למעלה, נתחיל מהפנים לחוץ: - keyof T= מחזיר union של כל המפתחות של שדות בטייפ- T
- Exclude<keyof T, K>= מוציא מתוך זה את השמות של השדות שאתה רוצה להשמיט בטייפ הסופי
- מיפוי [P in Exclude<keyof T, K>]מריץ לולאה על כל חבר מבודד מתוך ה-union שהתקבל מהשלב הקודם כאשר הטייפ המבודד מיוצג על ידי המשתנהP
- { [P in Exclude<keyof T, K>]: T[P]; }= מייצג טייפ שבו עבור כל- Pבלולאה, יש שדה בשם- Pוערך מהטייפ- T[P]= כלומר הטייפ המקורי של שדה זו בטייפ המקורי
 פסואודו קודוהנה פסאודו קוד לקינוח: function Omit(t: Type, u: UnionOfKeys) { const res = {} for (key of Omit(Keyof(t), u).split('|')) { res[key] = t[key] } return res }פשוט ביותר! הנושא המקורי (אחרי כל ההקדמות הארוכות)נחזור לנושא המקורי שהצגתי בכותרת 
 (אם חיכית בקוצר רוח לחלק זה, זה באמת לא כל כך מרעיש עולמות... מצטער...)ולשם הקדמה אביא את הטריגר שהביא אותי להבנה זו: 
 מישהו שאל: איך אפשר ליצור טייפ שבו מותרים רק שדהxו-yולא שום שדה נוסףנציין פה שיש טעות נפוצה אצל מתחילים ב-TS, שאם טייפ לא כולל שדה מסויים, אז ערך שיש בו שדה זו לא יכול להיות חבר בטייפ. זה לא נכון. 
 ב-TS, העובדה שטייפ לא כולל שדה מסויים, עדיין לא שולל שהשדה יכול להיות קיים.
 למשל:type HasFoo = { foo: string }גם אובייקט שיש לו שדה barיכול להיות חבר בקובצתHasFooconst hasBar = { bar: 1, foo: 'foo' } const test:HasFoo = hasBar // no error Object literal may only specify known properties Object literal may only specify known properties
 אם הייתי כותב בקיצור בקטע הנ"ל:const test:HasFoo = { bar: 1, foo: 'foo' }הייתי מקבל שגיאה זו: Object literal may only specify known properties, and 'bar' does not exist in type 'HasFoo'.(2353)
 אל תתנו לשגיאה זו להטעות אותך! העובדה ש-HasFooלא מצהיר על שדהbarלא מונע מחברי הסט לשאת שדות נוספות!
 ההתרעה של TS הוא מסיבה אחרת: מכיון שהמשתנה היחיד שקיבל את הערך של{ bar: 1, foo: 'foo' }יש לו טייפ שלא כולל את השדהbar, יוצא שבפועל אין דרך להשתמש בשדהbar. כי הגישה דרך משתנהtestאסור כי השדה לא כלול בטייפ שלו. רוב הסיכויים הם שקוד כזה הוא שגיאה של המתכנת, לכן TS מתריע.
 לעומת זאת, בדוגמה שהבאתי, ששמרתי את הערך בתוך משתנה אחר לפני ההשמה ל-test, אין על מה להתריע מכיון ששדהbarנשאר נגיש דרך משתנהhasBarנחזור לשאלה: איך אפשר לשלול שדות לא מוצהרות מטייפ ספוילר: התשובה היא שאי אפשר. וזה מתועד בתשובות ב-stack overflow ו-issues בגיטהאב אבל אני כעקשן ניסתי בכל זאת לפתור את זה על ידי הטייפ הבא: type T = { x: string y: number } type Exact<T> = { [K in keyof any]?: K extends keyof T ? T[K] : never }מקווה שכעת קל לכם להבין את הקוד בתרגום לאנגלית, זה אומר ככה: 
 בטייפExact<T>: קח את הטייפ של כל מפתח חוקי אפשרי (keyof any), עבור כל חבר מתוך מרכיביו בלולאה ותבדוק אם הוא מתוך המפתחות שלT, אם כן, הערך של המפתח בתוך התוצאה צריך להיות כמו ב-T(המקור); אחרת הטייפ צריך להיותnever- דבר שכמובן לא יכול להיות
 לכאורה יצא לנו טייפ שאוסר מפתחות מיותרותהתשובה היא מה שכתבתי בכותרת: "טייפ X לא זהה ל-union של כל הערכים האפשריים שהטייפ כולל" כלומר, הלולאה של K in keyof anyשמפרק אתkeyof anyלמרכיביו, ומעביר כל מרכיב בודד בלולאה, לא מפרק את הטייפstringלכל מחרוזת אפשרית. בהקשר זהstringהוא אטומי. כמו"כnumber.היה אפשר לחשוב אחרת, כי סה"כ הטייפ stringהוא סה"כ סט שמחובר מכל המחרוזות האפשריים ביקום. אבל לצערנו TS לא מסתכל על זה ככה. אלא מתייחסים ל-stringכאטום. ולכן בלולאהK in keyof anyיש התייחסות רק ל-stringו-numberו-symbolולא לערכים הפרטיים שמרכיבים אותם. כמובן, אם ככה, ההמצאה שלי לא תעבוד כי התנאיK extends keyof Tאף פעם לא יתקייםהשלכה נוספתעוד דבר שאנשים מבקשים מפעם לפעם (עיין דברי ימי שאלות SO ואישיו-אים של GH) הוא טייפ שמייצג כל מחרוזת אפשרית חוץ ממחרוזות ידועות לשמצה. למשל type NotWeekday = Exclude<string, Weekday>אנשים מצפים שדבר כזה יעבוד. אבל זה לא עובד. ולפי הנ"ל זה מובן 
 נזכיר את קוד המקור שלExcludetype Exclude<T, U> = T extends U ? never : Tהטייפ מתבסס על העובדה שלפני בדיקת התנאי יש פירוק של Tלמרכיביו ובדיקת התנאי רץ על כל מרכיב בנפרד (Distributive Conditional Types)
 אבל לצערנו, הטייפstringהוא אטומי. הוא לא מתפרק לכל המחרוזות שמרכיבים אותו. לכן התנאיT extends Uאף פעם לא מתקיים
 תם ולא נשלם 
- 
 D dovid פיצל נושא זה ב D dovid פיצל נושא זה ב
 
