תגובות ל"צלילה לעומק TypeScript"
-
בפוסט זה אני הולך לשתף דבר שלמדתי לאחרונה ב-TS שהיה חידוש עבורי
אני משתמש בנושא זה כקרש קפיצה לכתוב מבוא כללי ל-TS. כדי להסביר את החידוש שגיליתי, אסביר בצורה מתומצתת את המושגים שצריכים להכיר כדי להבין את החידוש
(אם מישהו יתחיל להשתמש בשפה בזכותי והיה זה שכרי. פשוט חבל שיש כל כך הרבה מתכנתים מוכשרים בקהילה שלנו שלא משתמשים ב-TS מחוסר היכרות...)מכיון שאני כותב את הפוסט בהמשכים אשמח אם התגובות יהיו בנושא נפרד
התוכנית:
- קורס מזורז ב-TS
- קטעי הקוד המוקשים לכאורה
- התשובה
מקווה שיהיה קל לקריאה ולתועלת
ובכן, אעביר אותך קורס מזורז מאוד ב-TS:
מה זה טייפ
דרך אחת להסתכל על טייפ הוא ככה:
טייפ הוא סט (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 Types
TS לוקח את התחביר הזה שלב נוסף קדימה באמצעות 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'>
טוב, התעייפתי לבינתיים, אמשיך בעז"ה בהזדמנות הבאה...
@yossiz כתב בצלילה לעומק TS: טייפ X לא זהה ל-union של כל הערכים האפשריים שהטייפ כולל:
{ foo: string; bar number }
חסר נקודתיים.