引言:Stack Overflow 上的永恒问题
您是否曾好奇过,经验丰富的 TypeScript 开发者是如何获取 enum 值的名称的?如果您浏览过 Stack Overflow,很可能见过那个拥有数千次浏览的问题:"How to get names of enum entries?"。看起来似乎并不复杂?但实际上,许多新手(甚至一些老手)都会在这里栽跟头。
在本文中,我们不仅会给出现成的答案。我们还会深入 TypeScript 的底层,剖析 enum 的具体工作原理,编译器如何将其转换为 JavaScript,以及为什么某些获取名称的方法会导致意想不到的错误。准备好了吗?我们开始吧!
什么是 TypeScript 中的枚举,为什么需要它们的名称?
枚举(Enum)是一种定义一组命名常量的便捷方式。在 TypeScript 中,枚举分为数字型(默认)和字符串型。但是,如果您需要获取某个特定值的名称该怎么办?例如,您从 API 获取了状态码 403,但希望向用户显示一条易懂的消息 "Forbidden"。
enum HttpStatus { OK = 200, NotFound = 404, Forbidden = 403}
// 我们的任务:根据值 403 获取名称 "Forbidden"看起来 TypeScript 应该为此提供一个内置方法。但事实并非如此。您需要自己编写逻辑。而有趣的部分就从这里开始。
“直接”解决方案:Object.keys 和 Object.values
最显而易见的方法是使用 Object.keys() 或 Object.values() 方法。许多新手会写出类似这样的代码:
function getEnumName(enumObj: any, value: number): string | undefined { const keys = Object.keys(enumObj); return keys.find(key => enumObj[key] === value);}
console.log(getEnumName(HttpStatus, 403)); // "Forbidden" — 有效!看起来合情合理?是的。但如果您在数字型枚举上尝试这段代码,将会遇到一个惊喜。让我们看看我们的 HttpStatus 被编译成了什么。
底层揭秘:数字型枚举的编译
当 TypeScript 编译数字型枚举时,它会创建一个具有反向映射(reverse mapping)的对象。以下是编译后的 JavaScript 代码:
var HttpStatus;(function (HttpStatus) { HttpStatus[HttpStatus["OK"] = 200] = "OK"; HttpStatus[HttpStatus["NotFound"] = 404] = "NotFound"; HttpStatus[HttpStatus["Forbidden"] = 403] = "Forbidden";})(HttpStatus || (HttpStatus = {}));
// 最终 HttpStatus 对象看起来像这样:// { // "200": "OK", // "404": "NotFound", // "403": "Forbidden", // "OK": 200, // "NotFound": 404, // "Forbidden": 403 // }看到诀窍了吗?编译器同时添加了数字键和字符串键。因此,Object.keys(HttpStatus) 返回的不仅仅是 ["OK", "NotFound", "Forbidden"],还包括 ["200", "404", "403"]。这破坏了我们简单解决方案的逻辑!
要解决这个问题,需要过滤掉数字键:
function getEnumNameSafe(enumObj: any, value: number): string | undefined { const keys = Object.keys(enumObj).filter(key => isNaN(Number(key))); return keys.find(key => enumObj[key] === value);}但这样的代码看起来像是个补丁。有没有更好的方法?
字符串枚举:另一种情况
如果您使用字符串枚举,则不会创建反向映射。这简化了任务:
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT"}
// Object.keys(Direction) 只会返回 ["Up", "Down", "Left", "Right"] — 完美!但是,如果您需要根据值获取名称呢?同样的 Object.keys 方法也能很好地工作:
function getStringEn