====== 第四章:枚举与字面量类型 ======
===== 本章概述 =====
枚举(Enum)和字面量类型(Literal Types)是 TypeScript 提供的强大类型工具。它们可以让代码更具可读性和类型安全性。本章将深入讲解这些类型的定义、使用和最佳实践。
===== 4.1 枚举(Enum)详解 =====
枚举是一种为一组数值赋予友好名称的方式,提高代码的可读性和可维护性。
==== 4.1.1 数字枚举 ====
// 自动递增的数字枚举
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 访问枚举成员
console.log(Direction.Up); // 0
console.log(Direction.Down); // 1
// 反向映射
console.log(Direction[0]); // "Up"
console.log(Direction[1]); // "Down"
// 指定起始值
enum Direction2 {
Up = 1, // 1
Down, // 2
Left, // 3
Right // 4
}
// 完全指定值
enum StatusCode {
OK = 200,
NotFound = 404,
ServerError = 500
}
// 混合使用
enum Mixed {
A = 1,
B, // 2
C = 10,
D // 11
}
==== 4.1.2 字符串枚举 ====
字符串枚举没有反向映射,每个成员必须显式初始化。
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
// 使用
const myColor: Color = Color.Red;
console.log(myColor); // "RED"
// 没有反向映射
// console.log(Color["RED"]); // Error
// 字符串枚举在模板字符串中很有用
function getColorMessage(color: Color): string {
return `The selected color is ${color}`;
}
// 使用场景:HTTP 方法
enum HttpMethod {
Get = "GET",
Post = "POST",
Put = "PUT",
Delete = "DELETE",
Patch = "PATCH"
}
function request(url: string, method: HttpMethod): void {
console.log(`${method} ${url}`);
}
request("/api/users", HttpMethod.Get);
==== 4.1.3 异构枚举 ====
异构枚举混合了字符串和数字成员(不推荐,但在某些场景有用):
enum MixedEnum {
No = 0,
Yes = "YES",
Maybe = 2
}
// 使用场景:布尔转换
enum BooleanLikeEnum {
No = 0,
Yes = "YES"
}
==== 4.1.4 常量枚举(Const Enums) ====
使用 const 修饰的枚举在编译阶段完全删除,替换为内联常量。
const enum Direction {
Up = 1,
Down = 2,
Left = 3,
Right = 4
}
// 使用
const dir = Direction.Up;
// 编译后的 JavaScript(没有枚举定义):
// const dir = 1;
==== 4.1.5 枚举编译后的代码 ====
// TypeScript
enum Direction {
Up = 1,
Down,
Left,
Right
}
编译为 JavaScript:
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Left"] = 3] = "Left";
Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));
结果是一个同时支持正向和反向查找的对象:
Direction = {
1: "Up",
2: "Down",
3: "Left",
4: "Right",
Up: 1,
Down: 2,
Left: 3,
Right: 4
}
===== 4.2 字面量类型(Literal Types) =====
字面量类型允许将具体的值作为类型使用。
==== 4.2.1 字符串字面量类型 ====
// 定义字符串字面量类型
type Easing = "ease-in" | "ease-out" | "ease-in-out";
// 使用
let animationEasing: Easing = "ease-in";
// animationEasing = "linear"; // Error: Type '"linear"' is not assignable
// 使用场景:事件名称
type MouseEventType = "click" | "dblclick" | "mousedown" | "mouseup" | "mousemove";
function handleMouseEvent(eventType: MouseEventType, handler: () => void): void {
document.addEventListener(eventType, handler);
}
handleMouseEvent("click", () => console.log("Clicked!"));
// handleMouseEvent("scroll", () => {}); // Error
==== 4.2.2 数字字面量类型 ====
// 定义数字字面量类型
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
type HttpStatus = 200 | 404 | 500;
type Port = 80 | 443 | 3000 | 8080;
// 使用
function rollDice(): DiceRoll {
return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}
// 使用场景:API 版本
type ApiVersion = 1 | 2 | 3;
function callApi(version: ApiVersion): void {
console.log(`Calling API v${version}`);
}
==== 4.2.3 布尔字面量类型 ====
// 布尔字面量类型通常用于对象属性的精确控制
interface ValidationResult {
valid: true; // 只能是 true
message: string;
}
interface InvalidResult {
valid: false; // 只能是 false
errors: string[];
}
type Result = ValidationResult | InvalidResult;
function processResult(result: Result): void {
if (result.valid) {
console.log(result.message); // TypeScript 知道这是 ValidationResult
} else {
console.log(result.errors); // TypeScript 知道这是 InvalidResult
}
}
===== 4.3 联合类型(Union Types) =====
联合类型表示一个值可以是几种类型之一。
==== 4.3.1 基本联合类型 ====
// 基本类型的联合
let value: string | number;
value = "hello"; // OK
value = 42; // OK
// value = true; // Error
// 多个类型的联合
type ID = string | number;
type Status = "pending" | "success" | "error";
// 复杂类型的联合
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
==== 4.3.2 类型收窄(Narrowing) ====
function formatValue(value: string | number): string {
// 类型守卫
if (typeof value === "string") {
// TypeScript 知道这里是 string
return value.toUpperCase();
} else {
// TypeScript 知道这里是 number
return value.toFixed(2);
}
}
// 使用类型谓词
function isString(value: unknown): value is string {
return typeof value === "string";
}
function process(value: unknown): void {
if (isString(value)) {
console.log(value.length); // OK
}
}
==== 4.3.3 可辨识联合(Discriminated Unions) ====
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
side: number;
}
interface Triangle {
kind: "triangle";
base: number;
height: number;
}
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
case "triangle":
return (shape.base * shape.height) / 2;
default:
// 穷举检查
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
===== 4.4 类型别名(Type Aliases) =====
类型别名为类型创建新名称,使用 type 关键字。
==== 4.4.1 基本类型别名 ====
// 基本类型别名
type UserID = string;
type Age = number;
type IsActive = boolean;
// 使用
const userId: UserID = "user-123";
const age: Age = 25;
// 对象类型别名
type Point = {
x: number;
y: number;
};
type User = {
id: UserID;
name: string;
age: Age;
isActive: IsActive;
};
==== 4.4.2 联合类型别名 ====
// 联合类型别名
type Status = "pending" | "success" | "error";
type ID = string | number;
type Primitive = string | number | boolean | null | undefined;
// 复杂联合
type Event =
| { type: "click"; x: number; y: number }
| { type: "keypress"; key: string }
| { type: "scroll"; delta: number };
==== 4.4.3 函数类型别名 ====
// 函数类型别名
type Callback = (error: Error | null, result: string) => void;
type Predicate = (item: T) => boolean;
type Transformer = (input: T) => U;
// 使用
type CompareFunction = (a: T, b: T) => number;
const numberCompare: CompareFunction = (a, b) => a - b;
const stringCompare: CompareFunction = (a, b) => a.localeCompare(b);
===== 4.5 枚举 vs 联合类型 =====
| 特性 | 枚举(Enum) | 联合类型(Union) |
| 编译后代码 | 生成对象 | 完全擦除 |
| 反向映射 | 支持(数字枚举) | 不支持 |
| 扩展性 | 可以添加方法 | 纯粹类型 |
| 树摇优化 | 可能被保留 | 完全移除 |
| 常量值 | 可以 | 可以(as const) |
| 运行时访问 | 可以 | 不可以 |
==== 4.5.1 何时使用枚举 ====
// 1. 需要运行时访问
enum LogLevel {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3
}
function log(message: string, level: LogLevel): void {
if (level >= LogLevel.Warn) { // 运行时比较
console.error(message);
}
}
// 2. 需要反向映射
function getLogLevelName(level: number): string {
return LogLevel[level] || "Unknown";
}
// 3. 需要稳定的标识符
enum Permission {
Read = 1 << 0, // 1
Write = 1 << 1, // 2
Execute = 1 << 2 // 4
}
const userPermission = Permission.Read | Permission.Write;
==== 4.5.2 何时使用联合类型 ====
// 1. 纯类型安全,不需要运行时值
type Status = "loading" | "success" | "error";
// 2. 需要复杂的类型组合
type Response =
| { status: "success"; data: T }
| { status: "error"; error: Error };
// 3. 需要类型收窄
type Event =
| { type: "click"; x: number; y: number }
| { type: "keypress"; key: string };
function handle(event: Event): void {
if (event.type === "click") {
console.log(event.x, event.y); // TypeScript 知道是 click 事件
}
}
// 4. 使用 as const 替代常量枚举
const Colors = {
Red: "RED",
Green: "GREEN",
Blue: "BLUE"
} as const;
type Color = typeof Colors[keyof typeof Colors]; // "RED" | "GREEN" | "BLUE"
===== 4.6 高级枚举模式 =====
==== 4.6.1 带方法的枚举 ====
enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500
}
namespace HttpStatus {
export function isSuccess(status: HttpStatus): boolean {
return status >= 200 && status < 300;
}
export function getMessage(status: HttpStatus): string {
switch (status) {
case HttpStatus.OK:
return "Success";
case HttpStatus.NotFound:
return "Resource not found";
case HttpStatus.ServerError:
return "Internal server error";
default:
return "Unknown status";
}
}
}
// 使用
console.log(HttpStatus.isSuccess(HttpStatus.OK)); // true
console.log(HttpStatus.getMessage(HttpStatus.NotFound)); // "Resource not found"
==== 4.6.2 位标志枚举 ====
enum Permission {
None = 0,
Read = 1 << 0, // 1
Write = 1 << 1, // 2
Execute = 1 << 2, // 4
Delete = 1 << 3 // 8
}
namespace Permission {
export function has(permissions: Permission, permission: Permission): boolean {
return (permissions & permission) === permission;
}
export function add(permissions: Permission, permission: Permission): Permission {
return permissions | permission;
}
export function remove(permissions: Permission, permission: Permission): Permission {
return permissions & ~permission;
}
}
// 使用
let userPermission = Permission.Read | Permission.Write;
console.log(Permission.has(userPermission, Permission.Read)); // true
console.log(Permission.has(userPermission, Permission.Execute)); // false
userPermission = Permission.add(userPermission, Permission.Execute);
console.log(Permission.has(userPermission, Permission.Execute)); // true
===== 4.7 常量断言(Const Assertions) =====
as const 是创建字面量类型的强大工具。
==== 4.7.1 基本用法 ====
// 不使用 as const
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
// 类型:{ apiUrl: string; timeout: number }
// 使用 as const
const config2 = {
apiUrl: "https://api.example.com",
timeout: 5000
} as const;
// 类型:{ readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }
// 整个对象变为只读
// config2.timeout = 3000; // Error
// 数组使用 as const
const roles = ["admin", "user", "guest"] as const;
// 类型:readonly ["admin", "user", "guest"]
type Role = typeof roles[number]; // "admin" | "user" | "guest"
==== 4.7.2 替代枚举 ====
// 常量对象替代字符串枚举
const Direction = {
Up: "UP",
Down: "DOWN",
Left: "LEFT",
Right: "RIGHT"
} as const;
type Direction = typeof Direction[keyof typeof Direction];
// type Direction = "UP" | "DOWN" | "LEFT" | "RIGHT"
// 使用
const move = (direction: Direction): void => {
console.log(`Moving ${direction}`);
};
move(Direction.Up); // OK
// move("UP"); // OK
// move("FORWARD"); // Error
===== 4.8 实用类型模式 =====
==== 4.8.1 状态机模式 ====
type State =
| { type: "idle" }
| { type: "loading"; progress: number }
| { type: "success"; data: unknown }
| { type: "error"; error: Error };
class StateMachine {
private state: State = { type: "idle" };
transition(newState: State): void {
// 验证状态转换
const validTransitions: Record = {
idle: ["loading"],
loading: ["success", "error", "idle"],
success: ["idle"],
error: ["idle", "loading"]
};
const valid = validTransitions[this.state.type].includes(newState.type);
if (!valid) {
throw new Error(`Invalid transition from ${this.state.type} to ${newState.type}`);
}
this.state = newState;
}
getCurrentState(): State {
return this.state;
}
}
==== 4.8.2 配置对象模式 ====
const Config = {
environments: {
development: { apiUrl: "http://localhost:3000", debug: true },
staging: { apiUrl: "https://staging.api.com", debug: true },
production: { apiUrl: "https://api.example.com", debug: false }
}
} as const;
type Environment = keyof typeof Config.environments;
type ConfigType = typeof Config.environments[Environment];
function getConfig(env: Environment): ConfigType {
return Config.environments[env];
}
// 使用
const devConfig = getConfig("development");
console.log(devConfig.apiUrl); // "http://localhost:3000"
===== 4.9 本章小结 =====
本章我们学习了:
1. **枚举(Enum)** - 数字枚举、字符串枚举、常量枚举、异构枚举
2. **字面量类型** - 字符串、数字、布尔字面量类型
3. **联合类型** - 基本联合、类型收窄、可辨识联合
4. **类型别名** - 为复杂类型创建名称
5. **枚举 vs 联合** - 选择合适的工具
6. **常量断言** - 使用 as const 创建精确类型
===== 4.10 练习题 =====
==== 练习 1:枚举设计 ====
设计一个完整的 HTTP 状态码枚举,包含:
- 信息响应 (100-199)
- 成功响应 (200-299)
- 重定向 (300-399)
- 客户端错误 (400-499)
- 服务器错误 (500-599)
并添加方法判断状态码类型。
参考答案
enum HttpStatusCode {
// 信息响应
Continue = 100,
SwitchingProtocols = 101,
// 成功响应
OK = 200,
Created = 201,
Accepted = 202,
NoContent = 204,
// 重定向
MovedPermanently = 301,
Found = 302,
NotModified = 304,
// 客户端错误
BadRequest = 400,
Unauthorized = 401,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
// 服务器错误
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503
}
namespace HttpStatusCode {
export function isInformational(status: HttpStatusCode): boolean {
return status >= 100 && status < 200;
}
export function isSuccess(status: HttpStatusCode): boolean {
return status >= 200 && status < 300;
}
export function isRedirect(status: HttpStatusCode): boolean {
return status >= 300 && status < 400;
}
export function isClientError(status: HttpStatusCode): boolean {
return status >= 400 && status < 500;
}
export function isServerError(status: HttpStatusCode): boolean {
return status >= 500 && status < 600;
}
export function isError(status: HttpStatusCode): boolean {
return status >= 400;
}
}
// 使用
console.log(HttpStatusCode.isSuccess(HttpStatusCode.OK)); // true
console.log(HttpStatusCode.isClientError(HttpStatusCode.NotFound)); // true
==== 练习 2:可辨识联合 ====
实现一个完整的 Redux Action 类型系统:
// 实现以下 Action 类型:
// - ADD_TODO: { type: "ADD_TODO", payload: { text: string } }
// - TOGGLE_TODO: { type: "TOGGLE_TODO", payload: { id: number } }
// - DELETE_TODO: { type: "DELETE_TODO", payload: { id: number } }
// - SET_FILTER: { type: "SET_FILTER", payload: { filter: "all" | "active" | "completed" } }
// 1. 定义 Action 类型
// 2. 实现 reducer 函数处理所有 action
// 3. 确保类型安全
参考答案
// Action 类型定义
type AddTodoAction = {
type: "ADD_TODO";
payload: { text: string };
};
type ToggleTodoAction = {
type: "TOGGLE_TODO";
payload: { id: number };
};
type DeleteTodoAction = {
type: "DELETE_TODO";
payload: { id: number };
};
type SetFilterAction = {
type: "SET_FILTER";
payload: { filter: "all" | "active" | "completed" };
};
type TodoAction =
| AddTodoAction
| ToggleTodoAction
| DeleteTodoAction
| SetFilterAction;
// State 类型
interface Todo {
id: number;
text: string;
completed: boolean;
}
interface TodoState {
todos: Todo[];
filter: "all" | "active" | "completed";
}
// Reducer
function todoReducer(state: TodoState, action: TodoAction): TodoState {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [
...state.todos,
{
id: Date.now(),
text: action.payload.text,
completed: false
}
]
};
case "TOGGLE_TODO":
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: todo
)
};
case "DELETE_TODO":
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload.id)
};
case "SET_FILTER":
return {
...state,
filter: action.payload.filter
};
default:
// 穷举检查
const _exhaustiveCheck: never = action;
return _exhaustiveCheck;
}
}
==== 练习 3:常量断言实践 ====
使用 as const 实现一个完整的主题配置系统:
// 要求:
// 1. 定义颜色主题配置,包含 primary、secondary、background 等颜色
// 2. 定义字体配置
// 3. 定义间距配置
// 4. 确保所有配置都是字面量类型和只读的
// 5. 导出配置的类型
参考答案
const theme = {
colors: {
primary: "#007bff",
secondary: "#6c757d",
success: "#28a745",
danger: "#dc3545",
warning: "#ffc107",
info: "#17a2b8",
light: "#f8f9fa",
dark: "#343a40",
background: "#ffffff",
text: "#212529"
},
fonts: {
family: {
sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
mono: '"SF Mono", Monaco, monospace'
},
size: {
xs: "0.75rem",
sm: "0.875rem",
base: "1rem",
lg: "1.125rem",
xl: "1.25rem",
"2xl": "1.5rem",
"3xl": "1.875rem"
}
},
spacing: {
xs: "0.25rem",
sm: "0.5rem",
md: "1rem",
lg: "1.5rem",
xl: "2rem",
"2xl": "3rem"
},
borderRadius: {
none: "0",
sm: "0.125rem",
base: "0.25rem",
md: "0.375rem",
lg: "0.5rem",
full: "9999px"
}
} as const;
// 导出类型
type Theme = typeof theme;
type Colors = typeof theme.colors;
type ColorName = keyof Colors;
type FontSize = keyof typeof theme.fonts.size;
type Spacing = keyof typeof theme.spacing;
// 使用
function getColor(name: ColorName): string {
return theme.colors[name];
}
function getSpacing(size: Spacing): string {
return theme.spacing[size];
}
// 使用示例
const primaryColor = getColor("primary"); // "#007bff"
const padding = getSpacing("md"); // "1rem"
==== 练习 4:类型收窄应用 ====
实现一个类型安全的 API 响应处理器:
// 要求:
// 1. 定义 Loading、Success、Error 三种状态
// 2. 实现 isLoading、isSuccess、isError 类型守卫函数
// 3. 实现 handleResponse 函数,根据状态正确处理
interface LoadingState { /* ... */ }
interface SuccessState { /* ... */ }
interface ErrorState { /* ... */ }
type ApiState = LoadingState | SuccessState | ErrorState;
// 实现类型守卫
function isLoading(state: ApiState): state is LoadingState { /* ... */ }
function isSuccess(state: ApiState): state is SuccessState { /* ... */ }
function isError(state: ApiState): state is ErrorState { /* ... */ }
// 实现处理器
function handleResponse(state: ApiState): void { /* ... */ }
参考答案
// 状态定义
interface LoadingState {
status: "loading";
}
interface SuccessState {
status: "success";
data: T;
timestamp: Date;
}
interface ErrorState {
status: "error";
error: Error;
code: number;
}
type ApiState = LoadingState | SuccessState | ErrorState;
// 类型守卫函数
function isLoading(state: ApiState): state is LoadingState {
return state.status === "loading";
}
function isSuccess(state: ApiState): state is SuccessState {
return state.status === "success";
}
function isError(state: ApiState): state is ErrorState {
return state.status === "error";
}
// 响应处理器
function handleResponse(state: ApiState): void {
if (isLoading(state)) {
console.log("Loading...");
showSpinner();
} else if (isSuccess(state)) {
console.log("Success!", state.data);
console.log("Fetched at:", state.timestamp);
renderData(state.data);
} else if (isError(state)) {
console.error("Error:", state.error.message);
console.error("Code:", state.code);
showError(state.error);
}
}
// 辅助函数(模拟)
function showSpinner(): void {}
function renderData(data: T): void {}
function showError(error: Error): void {}
// 使用示例
const loadingState: ApiState = { status: "loading" };
const successState: ApiState = {
status: "success",
data: "Hello",
timestamp: new Date()
};
const errorState: ApiState = {
status: "error",
error: new Error("Network error"),
code: 500
};
handleResponse(loadingState);
handleResponse(successState);
handleResponse(errorState);
===== 扩展阅读 =====
- [[TypeScript:第五章_接口|下一章:接口]]
- [[https://www.typescriptlang.org/docs/handbook/enums.html|TypeScript 官方文档 - 枚举]]
- [[https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types|TypeScript 官方文档 - 字面量类型]]
- [[https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types|TypeScript 官方文档 - 联合类型]]