טייפסקריפט יכולה להיות פשוטה וכיפית כשאנחנו נשארים על השבילים המוכרים, אבל גם אפלה ומעניינת כשאנחנו יורדים מהשביל ומנסים לשלב את התחביר שלה בצורות לא צפויות. היום הופתעתי לגלות שאפשר לכתוב קוד שבכלל לא חשבתי שיעבוד אבל בסוף הוא עושה בדיוק את מה שרצית ועוד קצת. הנה ההדבקה והסבר אחריה:
type Letter<Word extends string, R extends string[] = []> =
Word extends `${infer H}${infer T}` ? Letter<T, [...R, H]> : R[number];
// works! h is one of the letters in "hello"
const x: Letter<"hello"> = "h";
// compilation error! x is not one of the letters in "hello"
const y: Letter<"hello"> = "x";
בקצרה הטיפוס Letter שהגדרתי מקבל מחרוזת בתור Generics ומייצר ממנה טיפס שהוא איחוד של כל האותיות במחרוזת. בואו נפרק את זה כדי להבין איך ולמה זה עובד:
הטיפוס הוא גנרי ומקבל שני פרמטרים: Word ו R, שזה קיצור של Result. ל R יש ערך ברירת מחדל אז אני לא מעביר אותו כשאני משתמש בטיפוס.
הטיפוס מוגדר בתור Conditional Type. זה אומר שאם Word באמת מתאים לצורה:
`${infer H}${infer T}`
אז התנאי אמיתי ונלך לסימן שאלה, ואם לא אז התנאי שלילי ונלך לנקודותיים. אותה תבנית מוזרה נקראת Template Literal והיא מגדירה מחרוזת שמורכבת משירשור של שני טיפוסים. בגלל שאני יודע ש Word הוא מחרוזת, אז H ו T הם חלקים של אותה מחרוזת. החלק H הוא האות הראשונה, ו T זה המשך המילה. צריך לזכור שבטייפסקריפט כל אות היא טיפוס שמתאים רק לאותה אות, כלומר אני יכול לכתוב:
// compiles OK
const a: "a" = "a";
// compilation error - expecting only the letter "a"
const b: "a" = "b";
לכן H יהיה טיפוס של האות הראשונה במחרוזת, ו T יהיה טיפוס של מחרוזת שמורכבת מכל האותיות פרט לראשונה. המילה hello מתאימה לזה ועבורה ה H יהיה האות h קטנה וה T יהיה המחרוזת ello
.
בגלל שהמחרוזת מתאימה אנחנו הולכים לחלק שאחרי הסימן שאלה והוא אומר שהגדרת הטיפוס היא רקורסיבית:
Letter<T, [...R, H]>
הטיפוס מוגדר להיות Letter של T, שזה רק הסוף של המילה, אבל הפעם הפרמטר השני R מקבל ערך התחלתי - כל מה שהיה בו קודם פלוס הטיפוס H כלומר האות h הקטנה. לכן השלב הראשון ברקורסיה היה המעבר:
Letter<"hello", []> => Letter<"ello", ["h"]>
בגלל שזו רקורסיה טייפסקריפט ימשיך לפענח עוד ועוד צעדים שלה עד שהמחרוזת תתרוקן ואז נקבל:
Letter<"", ["h", "e", "l", "l", "o"]>
ואז מגיעים לחלק שאחרי הנקודותיים:
R[number]
בגלל ש R הוא מערך עם אותיות שידועות כולן בזמן קומפילציה, אנחנו יכולים לדבר על הטיפוס R[0]
שהוא האות h, או הטיפוס R[1]
שהוא האות e. אנחנו גם יכולים לדבר על הטיפוס R[number]
שהוא איחוד של כל האותיות במערך, כי number יכול להתאים לכל מספר שהוא אינדקס חוקי במערך.
התוצאה כמו שכבר בטח הבנתם היא הטיפוס שמזהה שמשתנה קיבל אות מתוך המילה.
את הרעיון קיבלתי מוויליאם קנדילון, ואתם מוזמנים להמשיך לראות אותו גם בוידאו בו הוא בונה משחק וורדל שלם בעזרת טיפוסים בטייפסקריפט:
https://www.youtube.com/watch?v=JT30j4nhej4