写代码时经常碰到这种情况:状态用 0、1、2 表示,过两周回来看 if (status === 2) 完全想不起来 2 是什么。这种散落在代码里的裸数字叫「魔法数字」,而枚举(enum)就是 TypeScript 提供的解药——给一组相关常量起上名字。
数字枚举:默认从 0 开始
用 enum 关键字定义枚举,成员之间用逗号分隔。不手动赋值时,第一个成员从 0 开始自动编号。
enum Status {
Draft, // 0
Review, // 1
Published, // 2
}
let post: Status = Status.Published;
console.log(post); // 2
console.log(Status.Review); // 1
// 用枚举代替魔法数字,语义一眼就懂
function canEdit(status: Status): boolean {
return status === Status.Draft;
}
console.log(canEdit(Status.Draft)); // true
console.log(canEdit(Status.Review)); // false
枚举既是类型也是值:可以像类型一样标注变量,也可以像对象一样取成员。
手动赋值与反向映射
数字枚举支持手动赋值,未赋值的成员会接着上一个值继续递增。数字枚举还有个特别的能力——反向映射,能从值反查名字。
enum Level {
Low = 1,
Middle, // 自动变成 2
High = 10,
Max, // 自动变成 11
}
console.log(Level.Middle); // 2
console.log(Level.Max); // 11
// 反向映射:值 -> 名字
console.log(Level[2]); // 'Middle'
console.log(Level[10]); // 'High'
反向映射在调试和日志里很实用:拿到接口返回的 2,用 Level[2] 就能打出 'Middle'。
字符串枚举
成员也可以赋字符串值,这叫字符串枚举。它没有反向映射,但调试时打印出来的是可读的名字,而不是一个数字,实际项目里往往更受欢迎。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
function move(dir: Direction): string {
return `向 ${dir} 移动`;
}
console.log(move(Direction.Up)); // 向 UP 移动
console.log(Direction.Down); // DOWN
// console.log(Direction["UP"]); // 编译报错:字符串枚举没有反向映射
经验法则:需要序列化、要打印进日志的用字符串枚举;需要做数学运算或节省体积的用数字枚举。
枚举成员也能当类型用
枚举的每个成员本身就是一个类型,函数参数可以限定成「只接受某一个成员」。
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
// 只接受 Color.Red 这一个值
function toHex(c: Color.Red): string {
return c === Color.Red ? "#ff0000" : "";
}
console.log(toHex(Color.Red)); // '#ff0000'
// toHex(Color.Blue); // 编译报错:参数只能是 Color.Red
枚举与字面量联合类型怎么选
TypeScript 里还有另一种写法:字符串字面量联合类型。它更轻量,编译后直接消失;枚举则会生成真实的运行时对象。简单说:需要在运行时拿到这组值(遍历、反查、传给 JS 代码)就选枚举,只在类型层面约束就用字面量联合。
// 字面量联合:纯类型层面的约束,编译后不存在
type Dir = "up" | "down";
let d: Dir = "up"; // 只能是这两个字符串
小结
enum给一组常量起名字,专治魔法数字;数字枚举默认从 0 自增- 数字枚举支持反向映射
Level[2],字符串枚举没有但日志可读性更好 - 枚举成员本身也是类型,可以把参数收窄到单个成员
- 需要运行时对象选枚举,纯类型约束选字面量联合类型,按需选择
