مقدمة: السؤال الأبدي على Stack Overflow
\n\nهل تساءلت يومًا كيف يحصل المطورون المتمرسون في TypeScript على أسماء قيم enum؟ إذا كنت قد ألقيت نظرة على Stack Overflow، فمن المحتمل أنك رأيت سؤالًا بآلاف المشاهدات: "How to get names of enum entries?". قد يبدو الأمر بسيطًا؟ لكن في الممارسة العملية، يتعثر العديد من المبتدئين (وليس فقط) في هذه النقطة.
في هذه المقالة، لن نقدم إجابة جاهزة فقط. سنتعمق تحت الغطاء في TypeScript ونحلل كيفية عمل enum بالضبط، وكيف يحولها المترجم إلى JavaScript، ولماذا قد تؤدي بعض طرق الحصول على الأسماء إلى أخطاء غير متوقعة. هل أنت مستعد؟ لنبدأ!
ما هو enum في TypeScript ولماذا نحتاج إلى أسمائها؟
\n\nEnum (التعداد) هو طريقة ملائمة لتحديد مجموعة من الثوابت المسماة. في TypeScript، تكون إما رقمية (افتراضيًا) أو نصية. ولكن ماذا تفعل إذا كنت بحاجة إلى الحصول على اسم قيمة معينة؟ على سبيل المثال، تلقيت من API رمز الحالة 403، وتريد عرض رسالة مفهومة للمستخدم مثل "Forbidden".
enum HttpStatus {\n OK = 200,\n NotFound = 404,\n Forbidden = 403\n}\n\n// مهمتنا: الحصول على اسم "Forbidden" بالقيمة 403\n\n\nيبدو أن TypeScript يجب أن يوفر طريقة مدمجة لهذا الغرض. لكن لا. يجب كتابة منطق خاص بنا. وهنا يبدأ الجزء المثير للاهتمام.
\n\nالحل المباشر: Object.keys و Object.values
\n\nالطريقة الأكثر وضوحًا هي استخدام دوال Object.keys() أو Object.values(). يكتب العديد من المبتدئين شيئًا كهذا:
function getEnumName(enumObj: any, value: number): string | undefined {\n const keys = Object.keys(enumObj);\n return keys.find(key => enumObj[key] === value);\n}\n\nconsole.log(getEnumName(HttpStatus, 403)); // "Forbidden" — يعمل!\n\n\nيبدو منطقيًا؟ نعم. ولكن إذا جربت هذا الكود على enum رقمي، فستفاجأ. دعنا نرى كيف يتم ترجمة HttpStatus الخاص بنا.
تحت الغطاء: ترجمة enum الرقمي
\n\nعندما يترجم TypeScript enum الرقمي، يقوم بإنشاء كائن مع تخطيط عكسي (reverse mapping). إليك كيف يبدو JavaScript المترجم:
\n\nvar HttpStatus;\n(function (HttpStatus) {\n HttpStatus[HttpStatus["OK"] = 200] = "OK";\n HttpStatus[HttpStatus["NotFound"] = 404] = "NotFound";\n HttpStatus[HttpStatus["Forbidden"] = 403] = "Forbidden";\n})(HttpStatus || (HttpStatus = {}));\n\n// في النهاية، يبدو كائن HttpStatus هكذا:\n// { \n// "200": "OK", \n// "404": "NotFound", \n// "403": "Forbidden", \n// "OK": 200, \n// "NotFound": 404, \n// "Forbidden": 403 \n// }\n\n\nهل ترى الحيلة؟ يضيف المترجم مفاتيح رقمية ومفاتيح نصية. لذلك، سيعيد Object.keys(HttpStatus) ليس فقط ["OK", "NotFound", "Forbidden"]، بل أيضًا ["200", "404", "403"]. هذا يكسر منطق حلنا البسيط!
لتجاوز ذلك، نحتاج إلى تصفية المفاتيح الرقمية:
\n\nfunction getEnumNameSafe(enumObj: any, value: number): string | undefined {\n const keys = Object.keys(enumObj).filter(key => isNaN(Number(key)));\n return keys.find(key => enumObj[key] === value);\n}\n\n\nلكن هذا الكود يبدو غير أنيق. هل هناك طريقة أفضل؟
\n\nenum النصي: قصة مختلفة
\n\nإذا كنت تستخدم enum نصي، فلن يتم إنشاء تخطيط عكسي. هذا يبسط المهمة:
\n\nenum Direction {\n Up = "UP",\n Down = "DOWN",\n Left = "LEFT",\n Right = "RIGHT"\n}\n\n// Object.keys(Direction) سيعيد فقط ["Up", "Down", "Left", "Right"] — مثالي!\n\n\nولكن ماذا لو كنت بحاجة إلى الحصول على الاسم بناءً على القيمة؟ نفس Object.keys يعمل بشكل ممتاز:
function getStringEn