枚举基础
概述
枚举(Enum)是 TypeScript 提供的一种特殊类型,用于定义一组命名的常量。枚举允许我们为一组相关的值赋予有意义的名称,提高代码的可读性和可维护性。枚举在 TypeScript 中既是一种类型,也是一个值,可以在运行时访问。理解枚举对于管理常量集合、状态码、配置选项等场景非常重要。
什么是枚举
枚举是一组具有名称的常量的集合。与直接使用数字或字符串相比,枚举提供了更好的类型安全性和代码可读性。
为什么使用枚举
使用枚举的主要优势:
- 类型安全:枚举提供了编译时的类型检查,避免使用无效的值
- 可读性:使用有意义的名称代替魔法数字或字符串
- 可维护性:集中管理常量,修改时只需在一处更新
- 智能提示:IDE 可以提供自动完成和类型提示
- 重构友好:重命名枚举成员时,所有引用会自动更新
枚举 vs 其他方式
typescript
// ❌ 不使用枚举:使用魔法数字
function setStatus(status: number): void {
if (status === 0) {
// 待处理
} else if (status === 1) {
// 处理中
} else if (status === 2) {
// 已完成
}
}
setStatus(0); // 不直观,容易出错
setStatus(99); // 无效值,但不会报错
// ❌ 不使用枚举:使用字符串常量
const STATUS_PENDING = "pending";
const STATUS_PROCESSING = "processing";
const STATUS_COMPLETED = "completed";
function setStatus(status: string): void {
// ...
}
setStatus(STATUS_PENDING); // 可以工作
setStatus("invalid"); // 无效值,但不会报错
// ✅ 使用枚举:类型安全且可读
enum Status {
Pending,
Processing,
Completed
}
function setStatus(status: Status): void {
// ...
}
setStatus(Status.Pending); // ✅ 类型安全,可读性强
// setStatus(99); // ❌ 编译错误:类型不匹配基本语法
枚举声明
使用 enum 关键字声明枚举,枚举名使用 PascalCase(首字母大写的驼峰命名):
typescript
// 基本枚举声明
enum Direction {
Up,
Down,
Left,
Right
}
// 使用枚举
let direction: Direction = Direction.Up;
console.log(direction); // 0(默认从 0 开始)枚举成员访问
枚举成员可以通过枚举名和成员名访问:
typescript
enum Color {
Red,
Green,
Blue
}
// 访问枚举成员
const primaryColor: Color = Color.Red;
const secondaryColor: Color = Color.Green;
// 枚举既是类型也是值
console.log(Color.Red); // 0
console.log(Color.Green); // 1
console.log(Color.Blue); // 2
// 使用枚举作为类型
function setColor(color: Color): void {
console.log(`Setting color to: ${color}`);
}
setColor(Color.Blue); // "Setting color to: 2"枚举的类型特性
枚举在 TypeScript 中具有双重身份:
- 作为类型:可以用于类型注解
- 作为值:可以在运行时访问
typescript
enum UserRole {
Admin,
User,
Guest
}
// 作为类型使用
function checkPermission(role: UserRole): boolean {
return role === UserRole.Admin;
}
// 作为值使用
const currentRole: UserRole = UserRole.Admin;
console.log(currentRole); // 0
// 枚举成员的类型
const adminRole: UserRole.Admin = UserRole.Admin; // 字面量类型枚举类型
TypeScript 支持三种主要的枚举类型:
- 数字枚举(Numeric Enums):默认类型,成员值为数字
- 字符串枚举(String Enums):成员值为字符串
- 常量枚举(Const Enums):编译时内联的枚举
数字枚举
数字枚举是默认的枚举类型,成员值从 0 开始自动递增:
typescript
enum Status {
Pending, // 0
Processing, // 1
Completed, // 2
Cancelled // 3
}
console.log(Status.Pending); // 0
console.log(Status.Processing); // 1
console.log(Status.Completed); // 2
console.log(Status.Cancelled); // 3自定义数字枚举值
可以手动指定枚举成员的值:
typescript
enum Status {
Pending = 100, // 100
Processing = 200, // 200
Completed = 300, // 300
Cancelled = 400 // 400
}
console.log(Status.Pending); // 100
console.log(Status.Processing); // 200
// 部分指定值,后续成员自动递增
enum Status2 {
Pending = 10, // 10
Processing, // 11(自动递增)
Completed, // 12(自动递增)
Cancelled = 20, // 20
Failed // 21(自动递增)
}字符串枚举
字符串枚举的每个成员都必须是字符串字面量:
typescript
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
console.log(Direction.Up); // "UP"
console.log(Direction.Down); // "DOWN"
// 字符串枚举不能自动递增,必须为每个成员指定值
enum Color {
Red = "red",
Green = "green",
Blue = "blue"
}混合枚举(不推荐)
TypeScript 允许数字和字符串混合的枚举,但不推荐使用:
typescript
enum MixedEnum {
No = 0,
Yes = "yes"
}
// 不推荐:混合类型会导致类型检查困难使用示例
示例 1:状态管理
使用枚举管理应用状态:
typescript
enum AppState {
Loading,
Ready,
Error,
Offline
}
class Application {
private state: AppState = AppState.Loading;
setState(newState: AppState): void {
this.state = newState;
this.handleStateChange();
}
private handleStateChange(): void {
switch (this.state) {
case AppState.Loading:
console.log("应用正在加载...");
break;
case AppState.Ready:
console.log("应用已就绪");
break;
case AppState.Error:
console.log("应用发生错误");
break;
case AppState.Offline:
console.log("应用处于离线状态");
break;
}
}
getState(): AppState {
return this.state;
}
}
const app = new Application();
app.setState(AppState.Loading); // "应用正在加载..."
app.setState(AppState.Ready); // "应用已就绪"示例 2:HTTP 状态码
使用枚举定义 HTTP 状态码:
typescript
enum HttpStatus {
OK = 200,
Created = 201,
Accepted = 202,
NoContent = 204,
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
InternalServerError = 500,
BadGateway = 502,
ServiceUnavailable = 503
}
function handleResponse(status: HttpStatus): void {
if (status >= 200 && status < 300) {
console.log("请求成功");
} else if (status >= 400 && status < 500) {
console.log("客户端错误");
} else if (status >= 500) {
console.log("服务器错误");
}
}
handleResponse(HttpStatus.OK); // "请求成功"
handleResponse(HttpStatus.NotFound); // "客户端错误"
handleResponse(HttpStatus.InternalServerError); // "服务器错误"示例 3:用户权限
使用字符串枚举定义用户权限:
typescript
enum Permission {
Read = "read",
Write = "write",
Delete = "delete",
Admin = "admin"
}
interface User {
name: string;
permissions: Permission[];
}
function hasPermission(user: User, permission: Permission): boolean {
return user.permissions.includes(permission);
}
function canDelete(user: User): boolean {
return hasPermission(user, Permission.Delete) ||
hasPermission(user, Permission.Admin);
}
const user: User = {
name: "Alice",
permissions: [Permission.Read, Permission.Write]
};
console.log(hasPermission(user, Permission.Read)); // true
console.log(hasPermission(user, Permission.Delete)); // false
console.log(canDelete(user)); // false
const admin: User = {
name: "Bob",
permissions: [Permission.Admin]
};
console.log(canDelete(admin)); // true示例 4:配置选项
使用枚举定义配置选项:
typescript
enum LogLevel {
Debug = "debug",
Info = "info",
Warn = "warn",
Error = "error"
}
enum Environment {
Development = "development",
Staging = "staging",
Production = "production"
}
interface AppConfig {
logLevel: LogLevel;
environment: Environment;
port: number;
}
function createConfig(
logLevel: LogLevel,
environment: Environment
): AppConfig {
return {
logLevel,
environment,
port: environment === Environment.Production ? 443 : 3000
};
}
const config = createConfig(LogLevel.Info, Environment.Production);
console.log(config);
// {
// logLevel: "info",
// environment: "production",
// port: 443
// }示例 5:方向控制
使用枚举定义方向:
typescript
enum Direction {
North = "N",
South = "S",
East = "E",
West = "W"
}
class Robot {
private x: number = 0;
private y: number = 0;
private facing: Direction = Direction.North;
move(direction: Direction, steps: number = 1): void {
switch (direction) {
case Direction.North:
this.y += steps;
break;
case Direction.South:
this.y -= steps;
break;
case Direction.East:
this.x += steps;
break;
case Direction.West:
this.x -= steps;
break;
}
this.facing = direction;
}
getPosition(): { x: number; y: number; facing: Direction } {
return { x: this.x, y: this.y, facing: this.facing };
}
}
const robot = new Robot();
robot.move(Direction.North, 5);
robot.move(Direction.East, 3);
console.log(robot.getPosition());
// { x: 3, y: 5, facing: "E" }枚举的反向映射
数字枚举具有反向映射特性,可以通过值获取枚举成员名:
typescript
enum Status {
Pending,
Processing,
Completed
}
// 正向访问:通过名称获取值
console.log(Status.Pending); // 0
console.log(Status.Processing); // 1
// 反向映射:通过值获取名称
console.log(Status[0]); // "Pending"
console.log(Status[1]); // "Processing"
console.log(Status[2]); // "Completed"
// 使用反向映射
function getStatusName(status: Status): string {
return Status[status];
}
console.log(getStatusName(Status.Pending)); // "Pending"注意
字符串枚举没有反向映射特性。只有数字枚举支持反向映射。
类型检查示例
常见错误
typescript
enum Status {
Pending,
Processing,
Completed
}
// ❌ 错误:类型不匹配
function setStatus(status: Status): void {
// ...
}
setStatus(0); // Error: Argument of type '0' is not assignable to parameter of type 'Status'
setStatus("Pending"); // Error: Argument of type '"Pending"' is not assignable to parameter of type 'Status'
// ❌ 错误:访问不存在的枚举成员
console.log(Status.Invalid); // Error: Property 'Invalid' does not exist on type 'typeof Status'
// ❌ 错误:字符串枚举必须为每个成员指定值
enum Color {
Red, // Error: Enum member must have initializer
Green = "green",
Blue = "blue"
}正确写法
typescript
enum Status {
Pending,
Processing,
Completed
}
// ✅ 正确:使用枚举成员
function setStatus(status: Status): void {
// ...
}
setStatus(Status.Pending); // ✅ 正确
setStatus(Status.Processing); // ✅ 正确
// ✅ 正确:使用类型断言(不推荐,但可行)
setStatus(0 as Status); // 可以工作,但不推荐
// ✅ 正确:字符串枚举必须指定值
enum Color {
Red = "red",
Green = "green",
Blue = "blue"
}
// ✅ 正确:数字枚举可以部分指定值
enum Status2 {
Pending = 10,
Processing, // 自动为 11
Completed // 自动为 12
}注意事项
提示
- 枚举提供了类型安全和代码可读性
- 使用枚举代替魔法数字和字符串常量
- 枚举成员名使用 PascalCase(首字母大写的驼峰命名)
- 数字枚举支持反向映射,可以通过值获取名称
- 字符串枚举提供更好的调试体验(显示有意义的字符串)
- 枚举既是类型也是值,可以在运行时访问
注意
- 字符串枚举的每个成员都必须显式指定值
- 字符串枚举不支持反向映射
- 混合枚举(数字和字符串)不推荐使用
- 枚举在编译后会生成 JavaScript 对象,增加代码体积
- 数字枚举的值可以超出定义的成员范围(类型检查可能不够严格)
重要
- 枚举是 TypeScript 特有的特性,编译后会生成 JavaScript 代码
- 如果只需要类型检查而不需要运行时值,考虑使用联合类型(Union Types)
- 对于大型项目,枚举会增加 JavaScript 代码体积,需要权衡使用
- 枚举成员的值在编译时确定,不能动态计算(除了引用其他枚举成员)
信息
枚举的应用场景:
状态管理:应用状态、请求状态、订单状态等
enum AppState { Loading, Ready, Error }enum OrderStatus { Pending, Processing, Completed }
配置选项:日志级别、环境配置、主题设置等
enum LogLevel { Debug, Info, Warn, Error }enum Environment { Dev, Staging, Prod }
常量集合:HTTP 状态码、错误代码、权限类型等
enum HttpStatus { OK = 200, NotFound = 404 }enum Permission { Read, Write, Admin }
方向/位置:方向控制、坐标系统等
enum Direction { North, South, East, West }
类型标识:区分不同的数据类型或变体
enum UserType { Admin, User, Guest }
枚举 vs 联合类型:
| 特性 | 枚举 | 联合类型 |
|---|---|---|
| 运行时存在 | ✅ 是 | ❌ 否(编译时擦除) |
| 反向映射 | ✅ 数字枚举支持 | ❌ 不支持 |
| 代码体积 | 增加 | 不增加 |
| 类型安全 | ✅ | ✅ |
| 智能提示 | ✅ | ✅ |
| 适用场景 | 需要运行时值 | 仅需类型检查 |
选择建议:
- 如果需要运行时访问枚举值,使用枚举
- 如果只需要类型检查,考虑使用联合类型
- 如果需要反向映射,使用数字枚举
- 如果需要更好的调试体验,使用字符串枚举