תכנות פונקציונלי - האם יש דרך למנוע סוגי איחוד ב-TypeScript?
אני חדש בסוגי תנאי, אז ניסיתי את הדרך הסטטית הברורה ביותר, ללא הצלחה:
type NoUnion<Key> =
Key extends 'a' ? 'a' :
Key extends 'b' ? 'b' :
never;
type B = NoUnion<'a'|'b'>;
הסוג B הוא עדיין איחוד. מישהו יכול בבקשה ללמד אותי?
הנה מגרש משחקים.
תשובה #1
אני לא בטוח מה השימוש במצב הזה, אבל אנחנו יכולים לכפות את NoUnion ל-never אם הסוג שהועבר הוא סוג איחוד.
כפי שאחרים הזכירו סוגי תנאי מתפזרים על פני איחוד, זה נקרא סוגי תנאי מתפזרים
סוגי תנאי שבהם סוג הנבדק הוא פרמטר סוג חשוף נקראים סוגי תנאי מפוצלים. סוגי תנאי מפוצלים מחולקים אוטומטית על פני סוגי איחוד במהלך האינסטנציאציה. לדוגמה, אינסטנציאציה של T extends U ? X : Y עם ארגומנט הסוג A | B | C עבור T מוגדרת כ-(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).
המפתח שם הוא 'סוג ערום', אם נארוז את הסוג בסוג טאפול למשל, סוג מותנה כבר לא יהיה מפיץ.
type UnionToIntersection<U> =
(U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
type NoUnion<Key> =
// If this is a simple type UnionToIntersection<Key> will be the same type, otherwise it will an intersection of all types in the union and probably will not extend `Key`
[Key] extends [UnionToIntersection<Key>] ? Key : never;
type A = NoUnion<'a'|'b'>; // never
type B = NoUnion<'a'>; // a
type OtherUnion = NoUnion<string | number>; // never
type OtherType = NoUnion<number>; // number
type OtherBoolean = NoUnion<boolean>; // never since boolean is just true|false
הדוגמה האחרונה היא בעיה, מכיוון ש-boolean נתפס על ידי המהדר כ-true|false, NoUnion<boolean> יהיה למעשה never. בלי פרטים נוספים על מה בדיוק אתה מנסה להשיג, קשה לדעת אם זה שובר עסקה, אבל ניתן לפתור זאת על ידי התייחסות ל-boolean כמקרה מיוחד:
type NoUnion<Key> =
[Key] extends [boolean] ? boolean :
[Key] extends [UnionToIntersection<Key>] ? Key : never;
הערה: UnionToIntersection נלקח מכאן
תשובה #2
דרך אגב, ה'פשוטה יותר' שראיתי לנסות להמציא נראית ככה:
( הערה: הדברים הבאים אינם עובדים ב-TS אחרי גרסה 3.3, עקב microsoft/TypeScript#34504:
// type NotAUnion<T> = [T] extends [infer U] ?
// U extends any ? [T] extends [U] ? T : never : never : never;
במקום זאת ניתן להשתמש במה שבא לאחר מכן מכיוון שברירות המחדל עדיין מופעלות לפני ההפצה, לפחות לעת עתה: )
type NotAUnion<T, U = T> =
U extends any ? [T] extends [U] ? T : never : never;
זה אמור לעבוד (בבקשה בדוק את זה; לא בטוח למה קיבלתי את הגרסה המקורית בתשובתי לשאלה אחרת בצורה שגויה אבל זה תוקן עכשיו). זה רעיון דומה ל-UnionToIntersection: אתה רוצה לוודא שסוג T ניתן להקצאה לכל חלק של T אם אתה מפיץ אותו. באופן כללי זה נכון רק אם T הוא איחוד עם חלק אחד בלבד (שנקרא גם "לא איחוד").
בכל מקרה, התשובה של @TitianCernicovaDragomir גם היא בסדר גמור. פשוט רציתי להוציא את הגרסה הזו החוצה. לחיים.
