Typescript enum ממחרוזת JSON
האם יש דרך לעשות שאנום של TypeScript יהיה תואם למחרוזות מ-JSON?
לדוגמה:
enum Type { NEW, OLD }
interface Thing { type: Type }
let thing:Thing = JSON.parse('{"type": "NEW"}');
alert(thing.type == Type.NEW); // false
הייתי רוצה ש-thing.type == Type.NEW יהיה נכון. או יותר נכון, אני מאחל שהייתי יכול לציין שהערכים של enum יוגדרו כ-מחרוזות, לא כמספרים.
אני מודע לכך שאני יכול להשתמש ב-thing.type.toString() == Type[Type.NEW] אבל זה מסורבל ונראה כי זה עושה את ההערת הסוג של האנום מבלבלת ומטעה, מה שמבטל את המטרה שלה. ה-JSON למעשה לא מספק ערך אנום תקני, ולכן איני צריך להגדיר את הסוג של התכונה לאנום.
אז מה שאני עושה כרגע במקום זה הוא להשתמש בסוג מחרוזת עם קבועים סטטיים:
const Type = { NEW: "NEW", OLD: "OLD" }
interface Thing { type: string }
let thing:Thing = JSON.parse('{"type": "NEW"}');
alert(thing.type == Type.NEW); // true
זה נותן לי את השימוש שאני רוצה, אבל ההערת סוג string רחבה מדי ועלולה לגרום לטעויות.
אני קצת מופתע שלסופרסייט של JavaScript אין אנומים מבוססי מחרוזת. האם אני מפספס משהו? האם יש דרך אחרת לעשות את זה?
עדכן TS 1.8
שימוש בסוגי מחרוזות מילוליות הוא אלטרנטיבה נוספת (תודה ל-@basaret), אך כדי לקבל את השימוש הדומה ל-enum הרצוי (מעל) זה מחייב להגדיר את הערכים שלך פעמיים: פעם אחת בסוג מחרוזת מילולית, ופעם אחת כערך (קבוע או namespace):
type Type = "NEW" | "OLD";
const Type = {
NEW: "NEW" as Type,
OLD: "OLD" as Type
}
interface Thing { type: Type }
let thing:Thing = JSON.parse(`{"type": "NEW"}`);
alert(thing.type === Type.NEW); // true
זה עובד אבל לוקח הרבה קוד חוזר, מספיק כדי שאני לא משתמש בזה ברוב הזמן. בינתיים אני מקווה שההצעה ל-string enums בסופו של דבר תיכנס ל-roadmap.
עדכן TS 2.1
החיפוש בסוג החדש keyof מאפשר ליצור את סוג המחרוזת המילולית מתוך המפתחות של const או namespace, מה שהופך את ההגדרה לקצת פחות מיותרת:
namespace Type {
export const OLD = "OLD";
export const NEW = "NEW";
}
type Type = keyof typeof Type;
interface Thing { type: Type }
const thing: Thing = JSON.parse('{"type": "NEW"}');
thing.type == Type.NEW // true
עדכון TS 2.4
TypeScript 2.4 הוסיפה תמיכה באנומסים מסוג מחרוזת! הדוגמה לעיל הופכת ל:
enum Type {
OLD = "OLD",
NEW = "NEW"
}
interface Thing { type: Type }
const thing: Thing = JSON.parse('{"type": "NEW"}');
alert(thing.type == Type.NEW) // true
זה נראה כמעט מושלם, אבל עדיין יש קצת כאב לב:
- אתה עדיין צריך לכתוב את הערך פעמיים, כלומר
OLD="OLD", ואין בדיקה שאתה לא עושה טעות כתיב, כמוNEW="MEW"... זה כבר הכניס אותי לצרות בקוד אמיתי. יש כמה אי-סדירויות (אולי תקלות?) באופן שבו Enum נבדק לפי סוג, זה לא רק קיצור סוג למחרוזת, שזה מה שהיה נכון באמת. כמה בעיות שנתקלתי בהן:
enum Color { RED = "RED", BLUE = "BLUE", GREEN = "GREEN" } type ColorMap = { [P in Color]: number; } declare const color: Color; declare const map: ColorMap; map[color] // Error: Element implicitly has an 'any' type because type 'ColorMap' has no index signature. const red: Color = "RED"; // Type '"RED"' is not assignable to type 'Color'. const blue: Color = "BLUE" as "RED" | "BLUE" | "GREEN"; // Error: Type '"RED" | "BLUE" | "GREEN"' is not assignable to type 'Color'.הקוד המקביל עם
enum Colorשהוחלף בסוגי מחרוזות פועל היטב...
כן, אני חושב שיש לי OCD לגבי זה, אני פשוט רוצה את המנויים המושלמים שלי ב-JS. :)
תשובה #1
אם אתה משתמש ב-Typescript לפני גרסת 2.4, יש דרך להשיג זאת עם enums על ידי המרת הערכים של ה-enum שלך ל-any.
דוגמה ליישום הראשון שלך
enum Type {
NEW = <any>"NEW",
OLD = <any>"OLD",
}
interface Thing { type: Type }
let thing:Thing = JSON.parse('{"type": "NEW"}');
alert(thing.type == Type.NEW); // true
ל-Typescript 2.4 יש כבר תמיכה מובנית עבור enums של מחרוזות, כך שההמרה ל-any כבר לא תהיה נחוצה ותוכלו להשיג זאת ללא השימוש ב-String Literal Union Type, שזה בסדר עבור אימות והשלמה אוטומטית, אבל לא טוב כל כך לקריאות ותהליכי ריפקטורינג, תלוי בתרחיש השימוש.
תשובה #2
במקרה שמישהו עדיין מסתכל על השאלה הזו בשנת 2021:
@Aaron כתב בשאלה המקורית:
זה נראה כמעט מושלם, אבל עדיין יש קצת כאב לב:
אתה עדיין צריך [...]
enum Color { RED = "RED", BLUE = "BLUE", GREEN = "GREEN" } type ColorMap = { [P in Color]: number; } declare const color: Color; declare const map: ColorMap; map[color] // Error: Element implicitly has an 'any' type because type 'ColorMap' has no index signature. // [...]הקוד המקביל עם enum Color שהוחלף על ידי סוגי מחרוזת עובד בצורה טובה...
כן, אני חושב שיש לי OCD לגבי זה, אני פשוט רוצה את המנויים המושלמים שלי ב-JS. :)
1. keyof typeof enumObj
לגבי
הקוד המקביל עם enum Color שהוחלף על ידי סוגי מחרוזת עובד בצורה טובה...
השתמש במפעילים typeof ו-keyof בקשר שרשור.
type ColorKeys = keyof typeof Color
type ColorMap = { [P in ColorKeys]: number; } // will have strongly typed keys
אין יותר any מרומז כאשר ניגשים ל-map: ColorMap.
זה יעבוד גם עם enums מספריים (שיכולים (ויותר נכון שיעשו זאת בדרך כלל) להיות const).
ממדריך TypeScript - Enums בזמן הידור:
למרות ש-Enums הם אובייקטים אמיתיים שקיימים בזמן ריצה, מילת המפתח keyof פועלת אחרת ממה שאתה עשוי לצפות לאובייקטים טיפוסיים. במקום זאת, השתמש ב-keyof typeof כדי לקבל טיפוס שמייצג את כל המפתחות של Enum כמחרוזות.
2. ts-enum-util
בדוק את ts-enum-util, שמציע ממשקים עם טיפוס חזק עבור (כנראה) כל הצרכים שלך הקשורים לאנומים.
