枚举(Enum)和字面量类型(Literal Types)是 TypeScript 提供的强大类型工具。它们可以让代码更具可读性和类型安全性。本章将深入讲解这些类型的定义、使用和最佳实践。
枚举是一种为一组数值赋予友好名称的方式,提高代码的可读性和可维护性。
```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
} ```
字符串枚举没有反向映射,每个成员必须显式初始化。
```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"
} ```
使用 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
} ```
字面量类型允许将具体的值作为类型使用。
```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}`);
} ```
```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
}
} ```
联合类型表示一个值可以是几种类型之一。
```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
}
} ```
```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;
}
} ```
类型别名为类型创建新名称,使用 type 关键字。
```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<T> = (item: T) ⇒ boolean; type Transformer<T, U> = (input: T) ⇒ U;
使用 type CompareFunction<T> = (a: T, b: T) ⇒ number; const numberCompare: CompareFunction<number> = (a, b) ⇒ a - b; const stringCompare: CompareFunction<string> = (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; ```
```typescript 1. 纯类型安全,不需要运行时值 type Status = “loading” | “success” | “error”; 2. 需要复杂的类型组合 type Response<T> =
| { 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” ```
```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 ```
as const 是创建字面量类型的强大工具。
```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” ```
```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 ```
```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<State["type"], State["type"][]> = {
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;
}
} ```
```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” ```
本章我们学习了:
1. 枚举(Enum) - 数字枚举、字符串枚举、常量枚举、异构枚举 2. 字面量类型 - 字符串、数字、布尔字面量类型 3. 联合类型 - 基本联合、类型收窄、可辨识联合 4. 类型别名 - 为复杂类型创建名称 5. 枚举 vs 联合 - 选择合适的工具 6. 常量断言 - 使用 as const 创建精确类型
设计一个完整的 HTTP 状态码枚举,包含:
并添加方法判断状态码类型。
<details> <summary>参考答案</summary>
```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 ``` </details> ==== 练习 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. 确保类型安全 ``` <details> <summary>参考答案</summary> ```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;
}
} ```
</details>
使用 as const 实现一个完整的主题配置系统:
```typescript 要求: 1. 定义颜色主题配置,包含 primary、secondary、background 等颜色 2. 定义字体配置 3. 定义间距配置 4. 确保所有配置都是字面量类型和只读的 5. 导出配置的类型 ```
<details> <summary>参考答案</summary>
```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” ``` </details> ==== 练习 4:类型收窄应用 ==== 实现一个类型安全的 API 响应处理器: ```typescript 要求: 1. 定义 Loading、Success、Error 三种状态 2. 实现 isLoading、isSuccess、isError 类型守卫函数 3. 实现 handleResponse 函数,根据状态正确处理 interface LoadingState { /* … */ } interface SuccessState<T> { /* … */ } interface ErrorState { /* … */ } type ApiState<T> = LoadingState | SuccessState<T> | ErrorState; 实现类型守卫 function isLoading<T>(state: ApiState<T>): state is LoadingState { /* … */ } function isSuccess<T>(state: ApiState<T>): state is SuccessState<T> { /* … */ } function isError<T>(state: ApiState<T>): state is ErrorState { /* … */ }
实现处理器 function handleResponse<T>(state: ApiState<T>): void { /* … */ } ``` <details> <summary>参考答案</summary> ```typescript 状态定义 interface LoadingState {
status: "loading";
}
interface SuccessState<T> {
status: "success"; data: T; timestamp: Date;
}
interface ErrorState {
status: "error"; error: Error; code: number;
}
type ApiState<T> = LoadingState | SuccessState<T> | ErrorState;
类型守卫函数 function isLoading<T>(state: ApiState<T>): state is LoadingState { return state.status === “loading”; } function isSuccess<T>(state: ApiState<T>): state is SuccessState<T> { return state.status === “success”; } function isError<T>(state: ApiState<T>): state is ErrorState { return state.status === “error”; } 响应处理器 function handleResponse<T>(state: ApiState<T>): 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<T>(data: T): void {} function showError(error: Error): void {} 使用示例 const loadingState: ApiState<string> = { status: “loading” }; const successState: ApiState<string> = {
status: "success", data: "Hello", timestamp: new Date()
}; const errorState: ApiState<string> = {
status: "error",
error: new Error("Network error"),
code: 500
};
handleResponse(loadingState); handleResponse(successState); handleResponse(errorState); ```
</details>