为什么不推荐使用 TypeScript Enum
不推荐的主要原因
不可擦除语法
编译后,会生成额外的 JS 运行时代码;且无法 tree-shaking。
- Enum
- namespace 内的非类型声明
其它诸如 interface type a: number 等等都是可擦除语法,TS 编译后会直接移除。
enum Direction {
UP,
DOWN,
LEFT,
RIGHT,
}
// 不可擦除,编译后,会生成额外的运行时代码
var Direction;
(function (Direction) {
Direction[Direction["UP"] = 0] = "UP";
Direction[Direction["DOWN"] = 1] = "DOWN";
Direction[Direction["LEFT"] = 2] = "LEFT";
Direction[Direction["RIGHT"] = 3] = "RIGHT";
})(Direction || (Direction = {}));
// 另一个是 namespace,内部非类型声明也会生成额外的运行时代码
namespace MathUtils {
export function add(a: number, b: number): number {
return a + b;
}
}
现在三方 package 的 d.ts 类型定义文件内都不会包含 enum 定义;想象一下,如果大家用的都是 enum,导入类型时会产生多少无法 tree-shaking 的代码!
类型不安全
Enum 语法特性,枚举值默认从数字 0 开始,key/value 可以互相访问。
enum Direction {
UP,
DOWN,
LEFT,
RIGHT,
}
function doAction(direction: Direction) {}
doAction(Direction.UP) // ✅ 可以
doAction(0) // ✅ 可以
不支持枚举值字面量
enum Direction {
UP,
DOWN,
LEFT,
RIGHT,
}
function doAction(direction: Direction) {}
doAction(Direction.UP) // ✅ 可以
doAction('UP') // ❌ 不行
Enum 的替代方案
字符串联合类型
如果字符串语义比较好,不需要 JSDoc 提示,可以使用联合类型。
另外,不推荐魔法数字,即用数字来代表类型:type Status = 0 | 1 | 2 | 3
type Direction =
/**@deprecated JSDoc 注释提示 */
| 'UP'
| 'DOWN'
| 'LEFT'
| 'RIGHT';
function doAction(direction: Direction) {}
doAction('UP'); // ✅ 可以,没有 JSDoc 提示
doAction('OTHER'); // ❌ 不行
对象字面量 + as const
如果联合类型项语义一般,或者需要 JSDoc 标记,可以使用对象字面量 + as const。 这个更符合以前 enum 那样的使用习惯。
// 对象字面量
const DirectionMap = {
/** @deprecated JSDoc 注释提示 */
UP: 'UP',
DOWN: 'DOWN',
LEFT: 'LEFT',
RIGHT: 'RIGHT'
} as const; // 没有 as const 的话, value 都是 string 类型
// DirectionMap 内 value 字符串联合类型
type Direction = typeof DirectionMap[keyof typeof DirectionMap];
function doAction(direction: Direction) {}
doAction(DirectionMap.UP);// ✅ 可以,有 TSDoc 提示
doAction('UP'); // ✅ 可以,没有 TSDoc 提示
doAction('OTHER'); // ❌ 不行
组合使用
API 接口以前有很多魔法数字,接口字段声明上只能用联合类型简单表示。
// model-service.d.ts
// API TS 类型
/**`/api/model/services/${id}` */
export interface ModelServiceDetail {
Name: string;
// ...
ModelServiceStatus: ModelServiceStatus;
}
/** 模型服务状态 */
export type ModelServiceStatus = 0 | 1 | 2 | 3 | 5 | -1 | -2;
实际使用时,可以用对象字面量形式进行语义映射,使用 TS satisfies 关键词来约束对象字面量的值类型 到 接口字段声明的联合类型上。
// use/model-service.ts
import type { ModelServiceStatus } from '@/types/model-service';
export const ModelServiceStatusEnum = {
/** 0 启动中 */
STARTING: 0,
/** 1 就绪 */
RUNNING: 1,
/** 2 删除中 */
DELETEING: 2,
/** 3 启动失败 */
FAILED: 3,
/** 5 休眠中 */
SLEEPING: 5,
/** -1 已提交 */
ARCHIVED: -1,
/** -2 初始化中 */
INITIALIZING: -2,
} satisfies Record<string, ModelServiceStatus>; // satisfies 进行 value 类型约束
// model-service.vue
if(viewingService.Status === ModelServiceStatusEnum.STARTING) {
// ...
}
其它
- 当然如果业务项目以前一直在用的话还是可以继续沿用,重点在于认识到把 enum 当成对象字面量来使用,因为不可擦除特性,enum 它不仅仅是纯粹的类型声明。
- TypeScript 5.8 添加了
-erasableSyntaxOnly配置选项,开启后仅允许使用可擦除语法。 - Node.js 23.6.0 版本开始默认支持直接执行可擦除语法 的 TS 文件。