为什么不推荐使用 TypeScript Enum

不推荐的主要原因

不可擦除语法

编译后,会生成额外的 JS 运行时代码;且无法 tree-shaking

其它诸如 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) {
  // ...
}

其它