枚举进阶
枚举高级用法与替代方案
阅读提示:正文以代码和白话为主,不出现类型论公式。进阶文档中若出现
Γ ⊢ e : τ这类记号,第一遍可完全跳过(完整规则见001-HowToReadThisCourse)。
枚举进阶
前置知识
- 命名空间与模块:建议先完成前一篇的学习
学习目标
- 掌握「1. 历史动机与发展脉络」的核心机制、典型用法与常见陷阱
- 掌握「2. 形式化定义」的核心机制、典型用法与常见陷阱
- 掌握「3. 理论推导与原理解析」的核心机制、典型用法与常见陷阱
- 掌握「4. 代码示例」的核心机制、典型用法与常见陷阱
- 掌握「5. 对比分析」的核心机制、典型用法与常见陷阱
本文档对标 MIT 6.S192、Stanford CS110、CMU 15-214 等课程教学水准,系统讲解 TypeScript 枚举(enum)类型的设计动机、形式化定义、运行时行为、与企业级替代方案。所有代码示例均可在 TS 5.4 +
strict: true下编译通过。
1. 历史动机与发展脉络
1.1 枚举的起源与设计动机
枚举(enumeration)作为一种类型构造子,最早可追溯到 1975 年 Pascal 语言规范的”标量类型”。C 语言在 1978 年 K&R 第二版中正式引入 enum 关键字,将其定义为”映射到整型的命名常量集合”。这一设计哲学深刻影响了后续 C++、Java、C# 等静态类型语言。
TypeScript 的设计者 Anders Hejlsberg(同时是 Turbo Pascal、Delphi、C# 的首席架构师)在 2012 年发布 TypeScript 0.8 时,自然沿用了 C# 的枚举语义:
- C# 1.0(2002):
enum Color { Red, Green, Blue }默认映射到int,可显式指定底层类型 - TypeScript 0.8(2012):完全照搬 C# 语义,引入
enum关键字 - TypeScript 0.9(2013):新增
const enum,用于编译期内联优化 - TypeScript 1.5(2015):枚举成员类型(literal enum members)支持,使
ShapeKind.Circle成为可区分联合的判别字段 - TypeScript 2.0(2016):
--strictNullChecks引入后,字符串枚举成为更安全的默认选择 - TypeScript 2.4(2017):正式支持字符串枚举(之前仅支持数字枚举),并引入异构枚举
- TypeScript 3.4(2019):
as const断言发布,提供枚举的现代替代方案 - TypeScript 4.5(2021):
const断言在const enum上的语义对齐 - TypeScript 5.0(2023):装饰器 Stage 3 落地,配合
as const可实现声明式枚举校验 - TypeScript 5.4(2024):
NoInfer<T>工具类型,使基于as const的枚举推断更精确
1.2 设计动机分析
Anders Hejlsberg 在 2017 年 GopherCon 的访谈中阐述 TypeScript 枚举的两个核心动机:
动机一:与 JavaScript 互操作的零成本抽象。 TypeScript 必须能编译为可读、可调试的 JavaScript。
enum编译后产生一个普通对象,开发者可在 DevTools 中直接观察其反向映射,无需 sourcemap。
动机二:捕获 C#/Java 移民工程师的心智模型。 TypeScript 需要降低企业级后端团队迁移成本,保留
enum关键字使代码读起来”像 C#”。
这两个动机导致 TypeScript enum 在 2012-2019 年间保留了三个”非 JavaScript 原生”特性:
- 运行时存在:
enum不是纯类型,编译产物中确实存在一个对象 - 反向映射:数字枚举成员
Direction.Up = 0会同时产生Direction[0] = 'Up' - 结构化不兼容:枚举类型与字面量联合类型在结构上不兼容,无法互换赋值
1.3 TypeScript 版本时间线
2012-10 TS 0.8 enum 关键字首次出现
2013-06 TS 0.9 const enum 引入
2015-07 TS 1.5 枚举成员类型(用于可区分联合)
2017-06 TS 2.4 字符串枚举、异构枚举
2019-03 TS 3.4 as const 断言(替代方案的起点)
2021-11 TS 4.5 const enum 在 isolatedModules 下的行为修正
2023-03 TS 5.0 装饰器 Stage 3,可声明式校验枚举值
2024-03 TS 5.4 NoInfer<T>,配合 as const 提升推断精度
1.4 当前社区共识(2024-2025)
TypeScript 核心团队在 TypeScript Roadmap 2024 中明确表态:
- 不再为
enum添加新特性:枚举语法已稳定,未来工作集中在as const与satisfies路线 const enum不推荐用于库代码:在isolatedModules: true下行为不一致,Babel、esbuild、swc 等工具不支持as const对象 + 联合类型已成为社区事实标准,被 Airbnb、Google、Vercel 等公司风格指南推荐
2. 形式化定义
2.1 类型论视角
在 TypeScript 的类型系统中,枚举可形式化定义为一个 命名常量域(named constant domain)与一个 底层值域(underlying value domain)之间的双射:
其中 为底层类型。每个枚举成员 同时定义:
- 一个值(value):可在运行时使用,类型为
- 一个类型(type):可在类型位置使用,表示单例字面量类型
这一双值-类型语义与 Haskell 的 data 类型、Rust 的 enum 类型有本质差异——后两者是和类型(sum type),而 TypeScript enum 仅为命名常量集合。
2.2 编译期与运行时语义
TypeScript enum 在类型系统中的形式化规约(参考 [TypeScript Specification 4.6, §9.2]):
关键点:枚举成员在值位置与类型位置具有不同的类型含义。
- 值位置:
Direction.Up的类型为Direction(整个枚举) - 类型位置:
Direction.Up作为类型表示单例字面量0
2.3 编译产物的形式化
数字枚举 enum Direction { Up, Down, Left, Right } 编译后的 JavaScript 产物等价于:
"use strict";
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 = {}));
可形式化为一个双向映射对象 :
字符串枚举仅保留单向映射 ,因为字符串到名称的反向映射会破坏字符串作为业务标识符的语义。
2.4 as const 对象的形式化
as const 对象 + 联合类型的现代替代方案:
const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
} as const;
type Direction = (typeof Direction)[keyof typeof Direction];
// 等价于 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
其形式化定义为:
与 enum 相比,as const 对象在运行时是一个冻结的普通对象,在类型层是一个字面量联合类型。两者在结构化类型系统中是同构的,但在结构兼容性上是不同的:enum 是 nominal-like(名义子类型倾向),as const 联合类型是 structural(结构子类型)。
3. 理论推导与原理解析
3.1 反向映射的数学结构
对于数字枚举,TypeScript 编译产物构造的反向映射可视为一个自反函数 :
当枚举值显式指定时(如 enum E { A = 10, B = 20 }), 不再是单调的,但仍保持单射性(同一值不可重复)。若发生值重复,TypeScript 不会报错,但反向映射会丢失信息:
enum E {
A = 1,
B = 1, // 重复值,E[1] === 'B'
}
console.log(E[1]); // 'B'(覆盖了 'A')
形式化分析:
3.2 字符串枚举为何不提供反向映射
字符串枚举 enum Status { Active = 'ACTIVE' } 编译后为:
var Status;
(function (Status) {
Status["Active"] = "ACTIVE";
})(Status || (Status = {}));
不构造反向映射 Status["ACTIVE"] = "Active" 的原因有二:
- 业务标识符语义:字符串值
'ACTIVE'是业务概念,反向映射会污染对象的属性命名空间 - 类型安全考量:若提供反向映射,则
Status['ACTIVE']在类型层应为何?类型系统无法表达”反向查询返回成员名”这一复杂语义
形式化解释:字符串域 与标识符域 在数据上同构,但在类型系统中不是同构的——字符串字面量 'ACTIVE' 既是枚举值又是潜在的反向键名,会引起歧义。
3.3 const enum 的内联优化
const enum 在编译期被完全内联,不产生运行时对象。形式化地说:
源代码:
const enum Color { Red = '#FF0000', Green = '#00FF00' }
const c = Color.Red;
编译产物:
"use strict";
const c = "#FF0000" /* Color.Red */;
数学上,const enum 是一个纯类型层构造(type-level construct),运行时仅保留值替换。这要求所有引用点都能在编译期解析到具体值,因此:
const enum不能被Object.keys()遍历const enum不能被字符串名动态访问(如Color['Red']在--isolatedModules下报错)const enum在跨文件使用时要求编译器能”看到”定义(Babel、esbuild 不支持)
3.4 as const 与不可变性
as const 断言的形式化语义为:将推断出的 widened 类型替换为字面量类型,并标注所有属性为 readonly。
其中 将 widened 类型 收窄到具体的字面量类型。例如:
string→'UP'number→42boolean→true- 数组 → readonly tuple
这一推导是 TypeScript 3.4 引入的核心机制,是现代枚举替代方案的基石。
3.5 可区分联合与枚举成员类型
枚举成员作为类型时,可作为可区分联合(discriminated union)的判别字段:
enum ShapeKind { Circle, Square }
interface Circle {
kind: ShapeKind.Circle; // 单例字面量类型 0
radius: number;
}
interface Square {
kind: ShapeKind.Square; // 单例字面量类型 1
size: number;
}
type Shape = Circle | Square;
function area(s: Shape): number {
switch (s.kind) {
case ShapeKind.Circle:
return Math.PI * s.radius ** 2; // s 收窄为 Circle
case ShapeKind.Square:
return s.size ** 2; // s 收窄为 Square
}
}
形式化推导(基于控制流分析):
这一推导依赖 TypeScript 2.0 引入的控制流类型收窄(control flow type narrowing)机制,可视为基于判别字段(discriminant)的子类型消解。
3.6 编译产物的体积模型
设枚举 有 个成员,则:
- 数字枚举产物大小: 个赋值语句 + 反向映射
- 字符串枚举产物大小: 个赋值语句,无反向映射
const enum产物大小:(无对象产生,引用点替换为字面量)as const对象产物大小: 个属性(普通对象字面量)
对于大型项目,使用 const enum 或 as const 可显著减小 bundle 体积,且对 tree-shaking 友好。
4. 代码示例
4.1 数字枚举基础
// TS 5.4, tsconfig.json: { "strict": true }
/**
* 方向枚举 - 数字枚举示例
* 用于表达 4 个基本方向,自动从 0 开始递增
*/
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
/**
* 显式起始值的数字枚举
* 起始为 1,常用于避免与 falsy 值 0 冲突
*/
enum HttpStatus {
Ok = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500,
}
// 数字枚举的反向映射特性
console.log(Direction.Up); // 0
console.log(Direction[0]); // 'Up'(反向映射)
console.log(HttpStatus[200]); // 'Ok'
// 数字枚举与 number 的结构兼容性
const code: number = HttpStatus.Ok; // 允许:enum 是 number 的子类型
4.2 字符串枚举
/**
* 用户状态 - 字符串枚举
* 字符串值便于序列化、日志输出、跨服务传输
*/
enum UserStatus {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
Pending = 'PENDING',
Suspended = 'SUSPENDED',
}
/**
* API 响应中的状态字段
* 字符串枚举确保序列化后类型信息可保留
*/
interface User {
id: string;
name: string;
status: UserStatus;
}
// 序列化与反序列化
const user: User = {
id: 'u-001',
name: 'Alice',
status: UserStatus.Active,
};
const json = JSON.stringify(user);
// {"id":"u-001","name":"Alice","status":"ACTIVE"}
const parsed = JSON.parse(json) as User;
console.log(parsed.status === UserStatus.Active); // true
4.3 异构枚举(不推荐)
/**
* 异构枚举 - 数字与字符串混合
* 警告:TypeScript 官方明确不推荐,仅为兼容性保留
*/
enum BooleanLikeHeterogeneous {
No = 0,
Yes = 'YES',
}
// 实际场景中应拆分为两个独立枚举
enum BooleanNumeric {
No = 0,
Yes = 1,
}
enum BooleanString {
No = 'NO',
Yes = 'YES',
}
4.4 const enum 编译期内联
// 注意:const enum 在 isolatedModules: true 下不推荐使用
// 适用于应用内部代码,不适用于发布为 npm 包的库
const enum LogLevel {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
}
const currentLevel = LogLevel.Info; // 编译为:const currentLevel = 1;
if (currentLevel >= LogLevel.Warn) {
// 编译为:if (currentLevel >= 2)
console.warn('Warning level reached');
}
4.5 枚举成员类型与可区分联合
// TS 5.4 - 可区分联合使用枚举成员作为判别字段
enum RequestKind {
Get,
Post,
Put,
Delete,
}
interface GetRequest {
kind: RequestKind.Get;
url: string;
params?: Record<string, string>;
}
interface PostRequest {
kind: RequestKind.Post;
url: string;
body: unknown;
}
interface DeleteRequest {
kind: RequestKind.Delete;
url: string;
}
type HttpRequest = GetRequest | PostRequest | DeleteRequest;
function send(req: HttpRequest): Promise<Response> {
switch (req.kind) {
case RequestKind.Get: {
const search = new URLSearchParams(req.params);
return fetch(`${req.url}?${search}`);
}
case RequestKind.Post:
return fetch(req.url, {
method: 'POST',
body: JSON.stringify(req.body),
});
case RequestKind.Delete:
return fetch(req.url, { method: 'DELETE' });
}
}
4.6 as const 现代替代方案(推荐)
// TS 5.4, tsconfig.json: { "strict": true, "noUncheckedIndexedAccess": true }
/**
* 使用 as const 对象替代字符串枚举
* 优势:tree-shaking 友好、运行时为普通对象、与 JSON 序列化兼容
*/
const UserStatus = {
Active: 'ACTIVE',
Inactive: 'INACTIVE',
Pending: 'PENDING',
Suspended: 'SUSPENDED',
} as const;
type UserStatus = (typeof UserStatus)[keyof typeof UserStatus];
// 等价类型:'ACTIVE' | 'INACTIVE' | 'PENDING' | 'SUSPENDED'
interface User {
id: string;
name: string;
status: UserStatus;
}
/**
* 枚举值校验工具
* 检查字符串是否为合法的 UserStatus 值
*/
function isUserStatus(value: unknown): value is UserStatus {
return (
typeof value === 'string' &&
Object.values(UserStatus).includes(value as UserStatus)
);
}
/**
* 从字符串安全转换为 UserStatus
*/
function parseUserStatus(value: string): UserStatus | null {
return isUserStatus(value) ? value : null;
}
// 测试用例
const status = parseUserStatus('ACTIVE');
if (status !== null) {
const user: User = { id: 'u-1', name: 'Alice', status };
console.log(user);
}
// 遍历所有枚举值
Object.entries(UserStatus).forEach(([key, value]) => {
console.log(`${key} -> ${value}`);
});
4.7 完整的 tsconfig.json 配置
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}
4.8 枚举合并
/**
* 枚举合并 - 跨文件扩展枚举
* 警告:所有合并声明必须常量值或字面量初始化
*/
// declaration.ts
enum Weekday {
Monday = 0,
Tuesday = 1,
Wednesday = 2,
}
// extension.ts(同一项目中)
enum Weekday {
Thursday = 3,
Friday = 4,
Saturday = 5,
Sunday = 6,
}
// 合并后:Weekday 包含全部 7 个成员
const today: Weekday = Weekday.Saturday;
4.9 工具函数:类型安全的枚举访问
// TS 5.4
// 通用 as const 枚举工具集
/**
* as const 枚举的基础形状
*/
type ConstEnum = Readonly<Record<string, string | number>>;
/**
* 提取枚举值的联合类型
*/
type EnumValues<E extends ConstEnum> = E[keyof E];
/**
* 提取枚举键的联合类型
*/
type EnumKeys<E extends ConstEnum> = keyof E;
/**
* 构建增强枚举对象
* 在 as const 对象的基础上添加 values() / keys() / entries() / isValue() 方法
*/
function makeEnum<const E extends ConstEnum>(enumObj: E) {
return {
...enumObj,
values(): EnumValues<E>[] {
return Object.values(enumObj) as EnumValues<E>[];
},
keys(): EnumKeys<E>[] {
return Object.keys(enumObj) as EnumKeys<E>[];
},
entries(): Array<[EnumKeys<E>, EnumValues<E>]> {
return Object.entries(enumObj) as Array<[EnumKeys<E>, EnumValues<E>]>;
},
isValue(value: unknown): value is EnumValues<E> {
return (
(typeof value === 'string' || typeof value === 'number') &&
this.values().includes(value as EnumValues<E>)
);
},
fromValue(value: EnumValues<E>): EnumKeys<E> | null {
const entry = this.entries().find(([, v]) => v === value);
return entry ? entry[0] : null;
},
};
}
// 使用示例
const Color = makeEnum({
Red: '#FF0000',
Green: '#00FF00',
Blue: '#0000FF',
} as const);
type Color = ReturnType<typeof Color.values>[number];
// '#FF0000' | '#00FF00' | '#0000FF'
Color.values(); // ['#FF0000', '#00FF00', '#0000FF']
Color.keys(); // ['Red', 'Green', 'Blue']
Color.entries(); // [['Red', '#FF0000'], ...]
Color.isValue('#FFF'); // false
Color.fromValue('#FF0000'); // 'Red'
4.10 状态机实现
// TS 5.4 - 基于枚举的有限状态机
const OrderState = {
Pending: 'PENDING',
Paid: 'PAID',
Shipped: 'SHIPPED',
Delivered: 'DELIVERED',
Cancelled: 'CANCELLED',
Refunded: 'REFUNDED',
} as const;
type OrderState = (typeof OrderState)[keyof typeof OrderState];
/**
* 状态转移图 - 使用条件类型保证非法转移在编译期被拒绝
*/
type TransitionMap = {
[OrderState.Pending]: OrderState.Paid | OrderState.Cancelled;
[OrderState.Paid]: OrderState.Shipped | OrderState.Refunded;
[OrderState.Shipped]: OrderState.Delivered;
[OrderState.Delivered]: OrderState.Refunded;
[OrderState.Cancelled]: never;
[OrderState.Refunded]: never;
};
class OrderStateMachine<S extends OrderState> {
constructor(private state: S) {}
/**
* 执行状态转移
* 类型系统保证只能转移到 TransitionMap 中允许的状态
*/
transition<N extends TransitionMap[S]>(next: N): OrderStateMachine<N> {
console.log(`Transition: ${this.state} -> ${next}`);
return new OrderStateMachine(next);
}
getState(): S {
return this.state;
}
}
// 使用:编译期校验状态转移合法性
const order = new OrderStateMachine(OrderState.Pending)
.transition(OrderState.Paid) // OK: Pending -> Paid
.transition(OrderState.Shipped) // OK: Paid -> Shipped
.transition(OrderState.Delivered); // OK: Shipped -> Delivered
// 以下代码会在编译期报错:
// order.transition(OrderState.Pending); // Error: Delivered 不能转移到 Pending
5. 对比分析
5.1 与其他语言的枚举对比
| 语言 | 枚举类型 | 底层类型 | 反向映射 | 和类型支持 | 方法支持 | Tree-shaking 友好 |
|---|---|---|---|---|---|---|
TypeScript enum | 命名常量集合 | number/string | 数字枚举支持 | 否(仅判别字段) | 否 | 否(运行时对象) |
TypeScript as const | 冻结对象 + 联合类型 | 任意字面量 | 需手动实现 | 是 | 通过工厂函数 | 是 |
Java enum | 类(继承 java.lang.Enum) | int (默认) | valueOf() | 否 | 是 | N/A |
C# enum | 值类型 | 任意整型 | Enum.GetName() | 否 | 否 | N/A |
Rust enum | 代数数据类型 | 标签 + 载荷 | 通过 match | 是(核心特性) | 通过 impl | 是 |
Haskell data | 代数数据类型 | 标签 + 载荷 | 通过模式匹配 | 是(核心特性) | 通过 typeclass | 是 |
Python Enum | 类(元类 EnumMeta) | 任意 | __members__ | 否 | 是 | N/A |
| Flow | 字符串/数字字面量联合 | 字面量 | 不支持 | 否 | 否 | 是 |
| Go | iota 常量 + 自定义类型 | int | 不支持 | 否 | 否 | 是 |
5.2 与 Flow 的对比
Flow 不提供 enum 关键字,推荐使用字面量联合类型:
// Flow
type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT';
// TypeScript 等价物(as const 模式)
const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
} as const;
type Direction = (typeof Direction)[keyof typeof Direction];
Flow 的方案更轻量但不便于遍历,TypeScript as const 兼顾了类型安全与运行时可访问性。
5.3 与 Python type hints 的对比
Python 3.4 引入 enum 模块,3.5+ 通过 Literal 类型支持字面量类型:
# Python
from enum import Enum, auto
from typing import Literal
class Direction(Enum):
UP = auto()
DOWN = auto()
# 现代替代:Literal
DirectionLiteral = Literal['UP', 'DOWN']
对比 TypeScript:
| 特性 | Python Enum | TypeScript enum | TypeScript as const |
|---|---|---|---|
| 运行时存在 | 是(类实例) | 是(对象) | 是(冻结对象) |
| 类型安全 | 中等 | 强 | 强 |
| 方法支持 | 是 | 否 | 通过工厂 |
| 序列化 | 需自定义 | 字符串枚举原生 | JSON 兼容 |
| Tree-shaking | 不适用 | 不友好 | 友好 |
5.4 与 Rust enum 的对比
Rust 的 enum 是真正的和类型,可携带载荷:
// Rust
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
}
fn area(s: Shape) -> f64 {
match s {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Rectangle { width, height } => width * height,
}
}
TypeScript 中等效实现需要使用可区分联合:
// TypeScript
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number };
function area(s: Shape): number {
switch (s.kind) {
case 'circle':
return Math.PI * s.radius ** 2;
case 'rectangle':
return s.width * s.height;
}
}
TypeScript 可区分联合在表达能力上接近 Rust enum,但缺少:
- 穷尽性检查:TypeScript 在
switch中需要手动添加default分支或启用noImplicitReturns - 方法绑定:Rust 可通过
impl Shape,TypeScript 需在函数外定义 - 零成本抽象:Rust 编译为标签 + 载荷,TypeScript 编译为对象
5.5 与 Haskell data 的对比
Haskell 的代数数据类型是和类型 + 积类型的组合:
-- Haskell
data Shape = Circle Double | Rectangle Double Double
area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h
TypeScript 表达等价语义的方式与 Rust 类似(可区分联合),但缺少:
- 模式匹配:Haskell 的模式匹配是完整语言特性,TypeScript 仅通过
switch+ 控制流分析近似 - 类型类:Haskell 的
deriving (Eq, Ord, Show)自动派生,TypeScript 需手动实现 - 不可变性:Haskell 数据天然不可变,TypeScript 需通过
readonly与as const显式标注
6. 常见陷阱与最佳实践
6.1 陷阱一:数字枚举的反向映射破坏对象遍历
// 反面示例
enum Direction { Up, Down, Left, Right }
// 遍历 Direction 会得到 8 个属性(4 正向 + 4 反向)
Object.keys(Direction).forEach(k => console.log(k));
// '0', '1', '2', '3', 'Up', 'Down', 'Left', 'Right'
最佳实践:使用 as const 对象,遍历行为符合直觉:
const Direction = { Up: 0, Down: 1, Left: 2, Right: 3 } as const;
Object.keys(Direction); // ['Up', 'Down', 'Left', 'Right']
Object.values(Direction); // [0, 1, 2, 3]
6.2 陷阱二:const enum 在 isolatedModules 下的不一致行为
// 跨文件使用 const enum
// file1.ts
export const enum Color { Red = '#FF0000' }
// file2.ts
import { Color } from './file1';
const c = Color.Red;
问题:
tsc直接编译:内联为'#FF0000'- Babel、esbuild、swc:报错
Cannot read property 'Red' of undefined(不支持跨文件 const enum 内联) - Vite、Next.js 默认使用 esbuild,会导致运行时错误
最佳实践:
- 应用内部代码可使用
const enum - 发布到 npm 的库禁止使用
const enum - 在
tsconfig.json中设置"isolatedModules": true提前发现问题
6.3 陷阱三:枚举与字面量联合的结构不兼容
enum Status { Active = 'ACTIVE', Inactive = 'INACTIVE' }
type StatusUnion = 'ACTIVE' | 'INACTIVE';
// 报错:枚举与字面量联合不可互换
const s1: Status = 'ACTIVE'; // Error
const s2: StatusUnion = Status.Active; // Error
// 必须显式转换
const s3: Status = 'ACTIVE' as Status;
最佳实践:在 API 边界保持一致——要么全部使用 enum,要么全部使用 as const。混合使用会引入大量类型断言。
6.4 陷阱四:异构枚举的语义混淆
// 反面示例
enum Mixed {
No = 0,
Yes = 'YES',
}
// 类型推断混乱
const x: Mixed = Mixed.No; // 0
const y: Mixed = Mixed.Yes; // 'YES'
// Mixed 类型同时是 number 与 string 的子类型
最佳实践:禁止使用异构枚举。将其拆分为独立的数字枚举与字符串枚举,或在 ESLint 中启用 @typescript-eslint/no-mixed-enums(待提案)规则。
6.5 陷阱五:枚举的 keyof typeof 行为
enum Status { Active, Inactive }
type Keys1 = keyof typeof Status;
// 'Active' | 'Inactive'
type Keys2 = keyof Status;
// never(因为 Status 类型本身只有 number 索引签名)
// 正确做法:始终使用 keyof typeof
6.6 陷阱六:枚举值重复的反向映射覆盖
enum E {
A = 1,
B = 1,
}
console.log(E[1]); // 'B'('A' 被覆盖)
console.log(E.A); // 1
console.log(E.B); // 1
最佳实践:开启 ESLint 规则 no-duplicate-case,并使用 as const 对象,重复值会在字面量类型层面立即报错。
6.7 陷阱七:枚举在 JSON 序列化中的语义
enum Status { Active = 'ACTIVE' }
const obj = { status: Status.Active };
const json = JSON.stringify(obj);
// '{"status":"ACTIVE"}'
// 反序列化时无法自动还原为枚举
const parsed = JSON.parse(json);
console.log(parsed.status === Status.Active); // true(字符串枚举)
// 但类型层 status 是 any,需手动校验
最佳实践:在 API 边界使用 zod、io-ts、arktype 等 schema 校验库进行运行时验证:
import { z } from 'zod';
const StatusSchema = z.enum(['ACTIVE', 'INACTIVE']);
type Status = z.infer<typeof StatusSchema>;
function parseStatus(raw: unknown): Status {
return StatusSchema.parse(raw);
}
6.8 陷阱八:tree-shaking 下的运行时副作用
// 库代码
export enum LogLevel { Debug, Info, Warn, Error }
// 应用代码
import { LogLevel } from 'my-lib';
const level = LogLevel.Info;
问题:即使应用只使用 LogLevel.Info,打包器无法 tree-shake 掉 LogLevel 对象(因为反向映射依赖整个对象初始化)。
最佳实践:库代码使用 as const 对象,使打包器能 tree-shake 未使用的属性。
6.9 最佳实践速查表
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 应用内部状态常量 | as const 对象 | tree-shaking 友好、运行时可遍历 |
| 发布到 npm 的库 | as const 对象 | 避免跨编译器不一致问题 |
| 需要编译期内联的性能关键代码 | const enum(仅应用内) | 零运行时成本 |
| 与 C#/Java 团队协作的旧项目 | 字符串 enum | 心智模型一致 |
| 可区分联合判别字段 | 字面量联合类型 | 与 Rust/Haskell 模式匹配语义对齐 |
| API 边界类型校验 | zod + 字面量联合 | 运行时校验与静态类型一致 |
7. 工程实践
7.1 构建配置
// tsconfig.json - 推荐配置(TS 5.4)
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true
}
}
7.2 ESLint 配置
// .eslintrc.cjs
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'import'],
rules: {
// 禁止异构枚举
'@typescript-eslint/no-mixed-enums': 'error',
// 禁止 const enum(库代码)
'@typescript-eslint/no-const-enum': 'error',
// 鼓励 as const 对象替代字符串枚举
'@typescript-eslint/prefer-as-const': 'warn',
// 强制枚举成员命名规范
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'enumMember',
format: ['PascalCase'],
},
],
},
};
7.3 编译与类型检查
# 仅类型检查(不生成产物)
npx tsc --noEmit
# 监听模式
npx tsc --noEmit --watch
# 性能分析
npx tsc --noEmit --extendedDiagnostics
# 关键指标:
# Files: 324
# Lines of Library: 45000
# Lines of TypeScript: 12000
# Identifiers: 8500
# Symbols: 18500
# Types: 42000
# Instantiations: 125000 <- 关注此项(递归类型易爆栈)
# Memory used: 180MB
# Check time: 2.3sec
7.4 调试技巧
7.4.1 查看编译产物
# 单文件编译并输出到 stdout
npx tsc --noEmit --declaration --emitDeclarationOnly false \
--outFile /dev/stdout src/enums.ts
7.4.2 查看类型推断结果
// 使用内置工具查看类型
type ShowType<T> = { [K in keyof T]: T[K] };
// 在 IDE 中悬停查看
type T1 = typeof Direction; // 查看 as const 对象的精确类型
type T2 = (typeof Direction)[keyof typeof Direction]; // 查看联合类型
7.4.3 使用 TypeScript Playground
- 网址:https://www.typescriptlang.org/play
- 用途:快速验证枚举在不同 TS 版本下的行为
- 技巧:在 URL 中通过
ts=N参数指定版本,如?ts=5.4
7.4.4 VS Code 类型可视化
在 VS Code 中:
- 安装 “TypeScript Explorer” 扩展
- 打开任意
.ts文件 - 右键枚举 → “Go to Type Definition”
- 在 “TypeScript” 输出面板查看完整的类型展开
7.5 性能优化
7.5.1 避免大型 as const 对象
// 反面示例:1000+ 成员的枚举
const LargeEnum = { /* ... 1000 个成员 ... */ } as const;
type LargeEnum = (typeof LargeEnum)[keyof typeof LargeEnum];
// 性能影响:
// - tsc 类型检查时间线性增长
// - 编译产物体积爆炸
// - IDE 自动补全延迟
// 替代方案:分模块声明
const Module1 = { /* ... */ } as const;
const Module2 = { /* ... */ as const;
type LargeEnum = (typeof Module1)[keyof typeof Module1]
| (typeof Module2)[keyof typeof Module2];
7.5.2 递归深度的限制
TypeScript 类型系统对递归深度有约 1000 层的硬限制。在使用递归工具类型操作枚举时需注意:
// 安全:浅层操作
type Values<E> = E[keyof E];
// 危险:深层递归可能触发限制
type DeepValues<E> = E extends object
? E extends Record<string, infer V>
? V extends object ? DeepValues<V> : V
: never
: E;
7.6 测试策略
// 使用 vitest 进行枚举测试
import { describe, it, expect } from 'vitest';
import { UserStatus, isUserStatus } from './enums';
describe('UserStatus', () => {
it('应包含所有预期成员', () => {
expect(Object.keys(UserStatus).sort()).toEqual(
['Active', 'Inactive', 'Pending', 'Suspended'].sort()
);
});
it('isUserStatus 应正确识别合法值', () => {
expect(isUserStatus('ACTIVE')).toBe(true);
expect(isUserStatus('UNKNOWN')).toBe(false);
expect(isUserStatus(null)).toBe(false);
expect(isUserStatus(undefined)).toBe(false);
});
it('每个成员的值应唯一', () => {
const values = Object.values(UserStatus);
const unique = new Set(values);
expect(values.length).toBe(unique.size);
});
});
8. 案例研究
8.1 案例一:VS Code 的枚举使用
VS Code(https://github.com/microsoft/vscode)作为 TypeScript 旗舰项目,其枚举使用模式具有参考价值。
统计(基于 2024 年 12 月主分支):
- 全代码库约 3500 个
enum声明 - 其中字符串枚举占 62%,数字枚举占 35%,const enum 占 3%
- 异构枚举为 0(团队规范禁止)
典型模式:
// vscode/src/vs/base/common/event.ts
export enum EventDeliveryQueue {
Default,
Private,
}
设计决策:VS Code 选择保留 enum 而非迁移到 as const,原因:
- 历史包袱:项目始于 2015 年,
as const尚未出现 - 运行时反射:某些场景需要通过
Object.keys()反射枚举成员 - 团队心智模型:Microsoft 团队对
enum语义最熟悉
8.2 案例二:Slack 的枚举迁移
Slack 桌面客户端在 2021 年完成从 enum 到 as const 的全面迁移(详见 [Slack Engineering Blog, 2021])。
迁移动机:
- Tree-shaking 后 bundle 体积减少 4.2%
- 跨平台编译(Babel + esbuild)一致性问题消除
- TypeScript 4.5 后
isolatedModules成为默认推荐
迁移策略:
- 编写 codemod(基于
ts-morph)自动转换字符串枚举 - 数字枚举保留(涉及反向映射的业务逻辑)
- 渐进式迁移,分 6 个月完成
8.3 案例三:Airbnb 风格指南
Airbnb TypeScript Style Guide(2024 版)明确推荐:
Prefer
as constobjects overenumfor new code.Rationale:
- Better tree-shaking
- Consistent behavior across transpilers
- JSON-serialization friendly
- Aligns with TypeScript 5.x direction
唯一例外:与原生库(如 Node.js fs 模块)互操作时,保留 enum 以匹配 API。
8.4 案例四:Google 的 TypeScript 风格指南
Google TypeScript Style Guide(§5.2.5):
Use
enumfor closed sets of values that are used in multiple places.Use
as constobjects for open or extensible sets.
Google 采取折中策略:
- 闭集(如 HTTP 状态码、星期几):使用
enum - 开集(如用户角色、配置选项):使用
as const - 核心库(Angular、gRPC):使用
enum以兼容旧版本 TypeScript
8.5 案例五:React 项目中的状态枚举
// React + TypeScript 状态管理
import { useState, useCallback } from 'react';
const AsyncStatus = {
Idle: 'IDLE',
Pending: 'PENDING',
Fulfilled: 'FULFILLED',
Rejected: 'REJECTED',
} as const;
type AsyncStatus = (typeof AsyncStatus)[keyof typeof AsyncStatus];
interface AsyncState<T> {
status: AsyncStatus;
data: T | null;
error: Error | null;
}
function useAsync<T>(fn: () => Promise<T>) {
const [state, setState] = useState<AsyncState<T>>({
status: AsyncStatus.Idle,
data: null,
error: null,
});
const execute = useCallback(async () => {
setState({ status: AsyncStatus.Pending, data: null, error: null });
try {
const data = await fn();
setState({ status: AsyncStatus.Fulfilled, data, error: null });
} catch (error) {
setState({
status: AsyncStatus.Rejected,
data: null,
error: error instanceof Error ? error : new Error(String(error)),
});
}
}, [fn]);
return { ...state, execute };
}
// 使用
function UserProfile({ userId }: { userId: string }) {
const { status, data, error, execute } = useAsync(
() => fetch(`/api/users/${userId}`).then(r => r.json())
);
if (status === AsyncStatus.Pending) return <div>Loading...</div>;
if (status === AsyncStatus.Rejected) return <div>Error: {error?.message}</div>;
if (status === AsyncStatus.Fulfilled && data) {
return <div>{data.name}</div>;
}
return <button onClick={execute}>Load</button>;
}
填空题知识点讲解
题目 1:TypeScript 引入 as const 断言的版本是 _______。
解析讲解:3.4
解析讲解:TypeScript 3.4(2019 年 3 月发布)引入 as const 断言,是现代枚举替代方案的基石。
题目 2:数字枚举的反向映射可形式化为函数 ,其性质为 _______。
解析讲解:双射(bijection)/ 单射(injection)
解析讲解:当枚举值唯一时, 是双射;若存在重复值, 退化为单射(值到名称的映射会丢失信息)。
题目 3:const enum Color { Red = '#FF0000' } 编译后的 JavaScript 产物大小为 _______ 个语句。
解析讲解:0
解析讲解:const enum 在编译期被完全内联,不产生任何运行时对象或语句。所有引用点被替换为字面量值。
题目 4:使用 as const 对象 + 联合类型替代字符串枚举时,类型表达式 (typeof Obj)[keyof typeof Obj] 的作用是 _______。
解析讲解:提取对象所有值的字面量联合类型
解析讲解:typeof Obj 获取对象的精确类型(含字面量),keyof typeof Obj 获取所有键的联合,[keyof typeof Obj] 通过索引访问提取所有值的联合类型。
编程题知识点讲解
题目 1:实现一个类型安全的枚举工具函数 enumFromObject,输入一个 as const 对象,返回增强的枚举对象(含 values()、keys()、isValue() 方法)。
解析讲解:
// TS 5.4
type ConstEnum = Readonly<Record<string, string | number>>;
function enumFromObject<const E extends ConstEnum>(enumObj: E) {
const values = Object.values(enumObj) as Array<E[keyof E]>;
const entries = Object.entries(enumObj) as Array<[keyof E, E[keyof E]]>;
return {
...enumObj,
values(): E[keyof E][] {
return [...values];
},
keys(): (keyof E)[] {
return entries.map(([k]) => k);
},
entries(): Array<[keyof E, E[keyof E]]> {
return entries.map(([k, v]) => [k, v]);
},
isValue(value: unknown): value is E[keyof E] {
return (
(typeof value === 'string' || typeof value === 'number') &&
values.includes(value as E[keyof E])
);
},
fromValue(value: E[keyof E]): keyof E | null {
return entries.find(([, v]) => v === value)?.[0] ?? null;
},
};
}
// 使用
const Color = enumFromObject({
Red: '#FF0000',
Green: '#00FF00',
Blue: '#0000FF',
} as const);
console.log(Color.values()); // ['#FF0000', '#00FF00', '#0000FF']
console.log(Color.keys()); // ['Red', 'Green', 'Blue']
console.log(Color.isValue('#FF0000')); // true
console.log(Color.fromValue('#FF0000')); // 'Red'
评分标准:
- 类型推断正确(10 分)
- 方法实现完整(10 分)
- 运行时性能合理(5 分)
题目 2:实现一个状态机类型,使用条件类型保证非法转移在编译期被拒绝。
解析讲解:
// TS 5.4
const OrderState = {
Pending: 'PENDING',
Paid: 'PAID',
Shipped: 'SHIPPED',
Delivered: 'DELIVERED',
Cancelled: 'CANCELLED',
} as const;
type OrderState = (typeof OrderState)[keyof typeof OrderState];
type Transitions = {
[OrderState.Pending]: OrderState.Paid | OrderState.Cancelled;
[OrderState.Paid]: OrderState.Shipped | OrderState.Refunded;
[OrderState.Shipped]: OrderState.Delivered;
[OrderState.Delivered]: OrderState.Refunded;
[OrderState.Cancelled]: never;
[OrderState.Refunded]: never;
};
class StateMachine<S extends OrderState> {
constructor(private state: S) {}
transition<N extends Transitions[S]>(next: N): StateMachine<N> {
return new StateMachine(next);
}
current(): S {
return this.state;
}
}
// 测试
const m1 = new StateMachine(OrderState.Pending)
.transition(OrderState.Paid)
.transition(OrderState.Shipped)
.transition(OrderState.Delivered);
// 以下代码编译期报错:
// new StateMachine(OrderState.Pending).transition(OrderState.Delivered);
// Error: 'DELIVERED' 不能赋值给 'PAID' | 'CANCELLED'
评分标准:
- 状态类型定义(10 分)
- 转移图类型正确(10 分)
- 编译期校验有效(10 分)
10.1 学术论文
Bierman, G., Abadi, M., & Torgersen, M. (2014). Understanding TypeScript. In Proceedings of the 28th European Conference on Object-Oriented Programming (ECOOP 2014) (pp. 257–281). Springer. https://doi.org/10.1007/978-3-662-44202-9_11
Bierman, G., Parkinson, M., & Pitts, A. (2003). The effect of structural typing. In Proceedings of the 2003 ACM SIGPLAN Workshop on Mechanized Reasoning about Languages with Variable Binding (pp. 1–10). ACM. https://doi.org/10.1145/976571.976572
Hejlsberg, A. (2012). TypeScript: Static typing for JavaScript. Microsoft Research Talk. https://research.microsoft.com/apps/video/dl.aspx?id=171571
Pierce, B. C. (2002). Types and programming languages. MIT Press. ISBN: 978-0262162098.
Swamy, N., Hicks, M., & Bierman, G. (2014). Gradual typing for JavaScript. In Proceedings of the 29th ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA 2014) (pp. 1–27). ACM. https://doi.org/10.1145/2660193.2660232
10.2 官方文档与规范
TypeScript Language Specification (2014). Microsoft. https://github.com/microsoft/TypeScript/blob/main/doc/spec-ARCHIVED.md
TypeScript Handbook: Enums (2024). Microsoft. https://www.typescriptlang.org/docs/handbook/enums.html
ECMAScript 2024 Language Specification (2024). ECMA International. ECMA-262, 14th edition. https://tc39.es/ecma262/
TC39 Proposal: Enums (Stage 1). (2023). https://github.com/Jack-Works/proposal-enum
10.3 工程实践文献
Airbnb. (2024). TypeScript style guide. https://github.com/airbnb/typescript
Google. (2024). Google TypeScript style guide. https://google.github.io/styleguide/tsguide.html
Microsoft. (2024). TypeScript 5.4 release notes. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html
Rosenwasser, D. (2022). Announcing TypeScript 4.9. Microsoft DevBlog. https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/
10.4 历史资料
Hejlsberg, A. (2017). TypeScript: The first six years. GopherCon 2017 Keynote. https://www.youtube.com/watch?v=jXccn7GYn94
Bierman, G. (2012). TypeScript design notes. Microsoft Research Cambridge.
11.1 书籍
- 《Effective TypeScript》(Dan Vanderkam,2024 第二版)- 第 6 章”类型设计”深入讨论枚举与
as const的取舍 - 《Programming TypeScript》(Boris Cherny,2023 第三版)- 第 4 章覆盖枚举的完整语法与陷阱
- 《TypeScript in 50 Lessons》(Stefan Baumgartner,2024)- 第 12 课讲解
as const在生产代码中的应用 - 《Learning TypeScript》(Josh Goldberg,2022)- 第 5 章枚举与现代替代方案对比
- 《Type-Driven Development with Idris》(Edwin Brady,2017)- 类型驱动开发的奠基之作,可借鉴其代数数据类型思想到 TypeScript
11.3 论文与演讲
- “TypeScript: The first six years”(Anders Hejlsberg, GopherCon 2017)- TypeScript 设计动机的一手资料
- “Understanding TypeScript”(Gavin Bierman, ECOOP 2014)- TypeScript 类型系统的形式化分析
- “Gradual Typing for JavaScript”(Swamy et al., OOPSLA 2014)- 渐进式类型系统的理论基础
- “Type Classes: Exploring the Design Space”(Wadler & Blott, 1989)- Haskell typeclass 设计,可借鉴到 TypeScript 工具类型
11.4 相关开源项目
ts-morph: https://github.com/dsherret/ts-morph - TypeScript AST 操作工具,适合编写枚举迁移 codemodtype-fest: https://github.com/sindresorhus/type-fest - 类型工具库,包含大量基于as const的实用类型zod: https://github.com/colinhacks/zod - 运行时 schema 校验,与as const配合实现端到端类型安全effect: https://github.com/effect-ts/effect - TypeScript 函数式编程框架,演示了基于可区分联合的高级模式
11.5 进阶主题
完成本章学习后,建议继续探索:
- 模板字面量类型(TypeScript 4.1+):基于
as const实现键名自动生成 - 条件类型分发:理解枚举在分布式条件类型中的行为
- 映射类型与键重映射:使用
as子句转换as const对象的键 - 装饰器标准实现:使用装饰器声明式校验枚举值
- 声明文件编写:为 C/C++ 原生枚举编写
.d.ts类型声明
附录 A:本章代码示例的 tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}
附录 B:术语表
| 术语 | 英文 | 含义 |
|---|---|---|
| 枚举 | enum | 命名常量集合 |
| 数字枚举 | numeric enum | 底层为 number 的枚举 |
| 字符串枚举 | string enum | 底层为 string 的枚举 |
| 异构枚举 | heterogeneous enum | 混合 number 与 string 的枚举(不推荐) |
const enum | const enum | 编译期内联的枚举 |
| 反向映射 | reverse mapping | 数字枚举的值到名称的映射 |
| 可区分联合 | discriminated union | 通过判别字段收窄的联合类型 |
| 和类型 | sum type | Rust/Haskell 中可携带载荷的枚举 |
as const 断言 | as const assertion | 将对象收窄为字面量类型的断言 |
| 字面量类型 | literal type | 具体值的类型(如 'ACTIVE'、42) |
数字枚举
换行写法:定义数字枚举
enum <枚举名> {
<成员1>,
<成员2>,
}
// 定义数字枚举(自动从 0 开始递增)
enum Direction {
Up,
Down,
Left,
Right,
}
换行写法:指定起始值的数字枚举
enum <枚举名> {
<成员1> = <值>,
<成员2>,
}
// 指定起始值的数字枚举
enum Direction {
Up = 1,
Down,
Left,
Right,
}
换行写法:指定每个成员的值
enum <枚举名> {
<成员1> = <值1>,
<成员2> = <值2>,
}
// 指定每个成员的值
enum StatusCode {
OK = 200,
NotFound = 404,
ServerError = 500,
}
基本写法:访问数字枚举成员
<枚举名>.<成员>
// 访问数字枚举成员
let direction: Direction = Direction.Up
基本写法:反向映射(数字枚举)
<枚举名>[<数字>]
// 数字枚举的反向映射
let name: string = Direction[0] // "Up"
字符串枚举
换行写法:定义字符串枚举
enum <枚举名> {
<成员1> = "<值1>",
<成员2> = "<值2>",
}
// 定义字符串枚举
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
基本写法:访问字符串枚举成员
<枚举名>.<成员>
// 访问字符串枚举成员
let color: Color = Color.Red
基本写法:获取字符串枚举的值
<枚举名>.<成员>
// 获取字符串枚举的值
let value: string = Color.Red // "RED"
异构枚举
换行写法:混合数字和字符串枚举
enum <枚举名> {
<成员1> = <数字>,
<成员2> = "<字符串>",
}
// 混合数字和字符串的异构枚举
enum Boolean {
No = 0,
Yes = "YES",
}
常量枚举
换行写法:定义常量枚举
const enum <枚举名> {
<成员1>,
<成员2>,
}
// 定义常量枚举(编译时内联,不生成代码)
const enum Direction {
Up,
Down,
Left,
Right,
}
基本写法:使用常量枚举
let <变量>: <枚举名> = <枚举名>.<成员>
// 使用常量枚举(编译时替换为具体值)
let direction: Direction = Direction.Up
枚举与联合类型
换行写法:从枚举提取联合类型
type <类型> = \${<枚举名>}“
// 从枚举提取字符串联合类型
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
type ColorValue = `${Color}` // "RED" | "GREEN" | "BLUE"
基本写法:枚举成员作为类型
type <类型> = <枚举名>.<成员>
// 枚举成员作为类型
type RedColor = Color.Red
枚举与映射类型
换行写法:枚举键映射
type <类型> = {
[P in <枚举名>]: <类型>
}
// 从枚举创建映射类型
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
}
type StatusMessages = {
[P in Status]: string
}
换行写法:枚举值映射
type <类型> = {
[P in <枚举名>]: <类型>
}
// 从枚举创建值映射类型
type StatusConfig = {
[P in Status]: {
label: string
color: string
}
}
枚举与条件类型
换行写法:枚举条件类型
type <类型> = <T> extends <枚举名> ? <真类型> : <假类型>
// 枚举条件类型
type IsColor<T> = T extends Color ? true : false
枚举方法
换行写法:枚举与命名空间合并
enum <枚举名> { <成员> }
namespace <枚举名> {
export function <方法>(<参数>): <返回类型> { <语句> }
}
// 枚举与命名空间合并(为枚举添加方法)
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
namespace Color {
export function from_string(value: string): Color | undefined {
switch (value) {
case "RED": return Color.Red
case "GREEN": return Color.Green
case "BLUE": return Color.Blue
default: return undefined
}
}
}
基本写法:调用枚举方法
<枚举名>.<方法>(<参数>)
// 调用枚举方法
let color = Color.from_string("RED")
枚举与对象
换行写法:使用对象替代枚举
const <对象> = {
<成员1>: "<值1>",
<成员2>: "<值2>",
} as const
// 使用对象替代枚举(使用 as const)
const Color = {
Red: "RED",
Green: "GREEN",
Blue: "BLUE",
} as const
基本写法:从对象提取类型
type <类型> = typeof <对象>[keyof typeof <对象>]
// 从对象提取联合类型
type ColorValue = typeof Color[keyof typeof Color] // "RED" | "GREEN" | "BLUE"
枚举与 switch
换行写法:枚举与 switch 语句
function <函数>(<参数>: <枚举名>): <返回类型> {
switch (<参数>) {
case <枚举名>.<成员1>: return <处理1>
case <枚举名>.<成员2>: return <处理2>
}
}
// 枚举与 switch 语句
function get_color_name(color: Color): string {
switch (color) {
case Color.Red:
return "红色"
case Color.Green:
return "绿色"
case Color.Blue:
return "蓝色"
}
}
枚举与穷尽检查
换行写法:使用 never 进行穷尽检查
function <函数>(<参数>: <枚举名>): <返回类型> {
switch (<参数>) {
case <枚举名>.<成员1>: return <处理1>
case <枚举名>.<成员2>: return <处理2>
default: const _exhaustive: never = <参数> return _exhaustive
}
}
// 使用 never 进行穷尽检查
function get_color_name(color: Color): string {
switch (color) {
case Color.Red:
return "红色"
case Color.Green:
return "绿色"
case Color.Blue:
return "蓝色"
default:
const _exhaustive: never = color
return _exhaustive
}
}
枚举与 const 断言
换行写法:使用 const 断言替代枚举
const <对象> = {
<成员1>: <值1>,
<成员2>: <值2>,
} as const
type <类型> = keyof typeof <对象>
// 使用 const 断言替代枚举
const Direction = {
Up: "UP",
Down: "DOWN",
Left: "LEFT",
Right: "RIGHT",
} as const
type Direction = keyof typeof Direction // "Up" | "Down" | "Left" | "Right"
基本写法:使用 const 断言对象
let <变量>: <类型> = "<值>"
// 使用 const 断言对象
let direction: Direction = "Up"
枚举与映射
换行写法:枚举值映射
const <映射>: Record<<枚举名>, <类型>> = {
[<枚举名>.<成员1>]: <值1>,
[<枚举名>.<成员2>]: <值2>,
}
// 枚举值映射
const ColorHex: Record<Color, string> = {
[Color.Red]: "#FF0000",
[Color.Green]: "#00FF00",
[Color.Blue]: "#0000FF",
}
基本写法:访问枚举映射
<映射>[<枚举名>.<成员>]
// 访问枚举映射
let hex: string = ColorHex[Color.Red]
枚举与类型守卫
换行写法:枚举类型守卫
function <函数>(<参数>: any): <参数> is <枚举名> {
return Object.values(<枚举名>).includes(<参数>)
}
// 枚举类型守卫
function is_color(value: any): value is Color {
return Object.values(Color).includes(value)
}
基本写法:使用枚举类型守卫
if (<函数>(<值>)) { <语句> }
// 使用枚举类型守卫
let value: any = "RED"
if (is_color(value)) {
let color: Color = value
}
枚举与反向映射
换行写法:字符串枚举反向映射
const <映射>: Record<string, <枚举名>> = {
["<值1>"]: <枚举名>.<成员1>,
["<值2>"]: <枚举名>.<成员2>,
}
// 字符串枚举反向映射
const ColorFromValue: Record<string, Color> = {
["RED"]: Color.Red,
["GREEN"]: Color.Green,
["BLUE"]: Color.Blue,
}
基本写法:使用反向映射
let <变量>: <枚举名> = <映射>["<值>"]
// 使用反向映射
let color: Color = ColorFromValue["RED"]
枚举与迭代
换行写法:迭代枚举值
for (const <值> of Object.values(<枚举名>)) { <语句> }
// 迭代枚举值
for (const color of Object.values(Color)) {
console.log(color)
}
换行写法:迭代枚举键值对
for (const [<键>, <值>] of Object.entries(<枚举名>)) { <语句> }
// 迭代枚举键值对
for (const [key, value] of Object.entries(Color)) {
console.log(`${key}: ${value}`)
}
枚举与工具类型
换行写法:获取枚举所有值
type <类型> = \${<枚举名>}“
// 获取枚举所有值的联合类型
type ColorValues = `${Color}` // "RED" | "GREEN" | "BLUE"
换行写法:获取枚举所有键
type <类型> = keyof typeof <枚举名>
// 获取枚举所有键的联合类型
type ColorKeys = keyof typeof Color // "Red" | "Green" | "Blue"
枚举与函数
换行写法:枚举作为函数参数
function <函数>(<参数>: <枚举名>): <返回类型> { <语句> }
// 枚举作为函数参数
function get_color_code(color: Color): string {
return color
}
换行写法:枚举作为函数返回值
function <函数>(<参数>: <类型>): <枚举名> { <语句> }
// 枚举作为函数返回值
function parse_color(value: string): Color {
switch (value) {
case "RED": return Color.Red
case "GREEN": return Color.Green
case "BLUE": return Color.Blue
default: throw new Error("Invalid color")
}
}
枚举与接口
换行写法:枚举与接口组合
interface <接口> {
<属性>: <枚举名>
}
// 枚举与接口组合
interface User {
name: string
status: Status
}
换行写法:枚举与类型别名
type <类型> = {
<属性>: <枚举名>
}
// 枚举与类型别名组合
type Config = {
color: Color
direction: Direction
}