====== 第四章:枚举与字面量类型 ====== ===== 本章概述 ===== 枚举(Enum)和字面量类型(Literal Types)是 TypeScript 提供的强大类型工具。它们可以让代码更具可读性和类型安全性。本章将深入讲解这些类型的定义、使用和最佳实践。 ===== 4.1 枚举(Enum)详解 ===== 枚举是一种为一组数值赋予友好名称的方式,提高代码的可读性和可维护性。 ==== 4.1.1 数字枚举 ==== ```typescript // 自动递增的数字枚举 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 字符串枚举 ==== 字符串枚举没有反向映射,每个成员必须显式初始化。 ```typescript 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 异构枚举 ==== 异构枚举混合了字符串和数字成员(不推荐,但在某些场景有用): ```typescript enum MixedEnum { No = 0, Yes = "YES", Maybe = 2 } // 使用场景:布尔转换 enum BooleanLikeEnum { No = 0, Yes = "YES" } ``` ==== 4.1.4 常量枚举(Const Enums) ==== 使用 const 修饰的枚举在编译阶段完全删除,替换为内联常量。 ```typescript const enum Direction { Up = 1, Down = 2, Left = 3, Right = 4 } // 使用 const dir = Direction.Up; // 编译后的 JavaScript(没有枚举定义): // const dir = 1; ``` ==== 4.1.5 枚举编译后的代码 ==== ```typescript // TypeScript enum Direction { Up = 1, Down, Left, Right } ``` 编译为 JavaScript: ```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 = {})); ``` 结果是一个同时支持正向和反向查找的对象: ```javascript Direction = { 1: "Up", 2: "Down", 3: "Left", 4: "Right", Up: 1, Down: 2, Left: 3, Right: 4 } ``` ===== 4.2 字面量类型(Literal Types) ===== 字面量类型允许将具体的值作为类型使用。 ==== 4.2.1 字符串字面量类型 ==== ```typescript // 定义字符串字面量类型 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 数字字面量类型 ==== ```typescript // 定义数字字面量类型 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 布尔字面量类型 ==== ```typescript // 布尔字面量类型通常用于对象属性的精确控制 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 基本联合类型 ==== ```typescript // 基本类型的联合 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) ==== ```typescript 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) ==== ```typescript 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 基本类型别名 ==== ```typescript // 基本类型别名 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 联合类型别名 ==== ```typescript // 联合类型别名 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 函数类型别名 ==== ```typescript // 函数类型别名 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 何时使用枚举 ==== ```typescript // 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 何时使用联合类型 ==== ```typescript // 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 带方法的枚举 ==== ```typescript 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 位标志枚举 ==== ```typescript 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 基本用法 ==== ```typescript // 不使用 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 替代枚举 ==== ```typescript // 常量对象替代字符串枚举 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 状态机模式 ==== ```typescript 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 配置对象模式 ==== ```typescript 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) 并添加方法判断状态码类型。
参考答案 ```typescript 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 类型系统: ```typescript // 实现以下 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. 确保类型安全 ```
参考答案 ```typescript // 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 实现一个完整的主题配置系统: ```typescript // 要求: // 1. 定义颜色主题配置,包含 primary、secondary、background 等颜色 // 2. 定义字体配置 // 3. 定义间距配置 // 4. 确保所有配置都是字面量类型和只读的 // 5. 导出配置的类型 ```
参考答案 ```typescript 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 响应处理器: ```typescript // 要求: // 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 { /* ... */ } ```
参考答案 ```typescript // 状态定义 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 官方文档 - 联合类型]]