typescript:第四章_枚举与字面量类型

第四章:枚举与字面量类型

枚举(Enum)和字面量类型(Literal Types)是 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
}

字符串枚举没有反向映射,每个成员必须显式初始化。

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);

异构枚举混合了字符串和数字成员(不推荐,但在某些场景有用):

enum MixedEnum {
  No = 0,
  Yes = "YES",
  Maybe = 2
}
 
// 使用场景:布尔转换
enum BooleanLikeEnum {
  No = 0,
  Yes = "YES"
}

使用 const 修饰的枚举在编译阶段完全删除,替换为内联常量。

const enum Direction {
  Up = 1,
  Down = 2,
  Left = 3,
  Right = 4
}
 
// 使用
const dir = Direction.Up;
 
// 编译后的 JavaScript(没有枚举定义):
// const dir = 1;
// 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 = {}));
 
结果是一个同时支持正向和反向查找的对象:
 
<code 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 字符串字面量类型 ====
 
<code 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
// 定义数字字面量类型
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}`);
}
// 布尔字面量类型通常用于对象属性的精确控制
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
  }
}

联合类型表示一个值可以是几种类型之一。

// 基本类型的联合
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;
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
  }
}
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 关键字。

// 基本类型别名
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;
};
// 联合类型别名
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 };
// 函数类型别名
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);
特性 枚举(Enum) 联合类型(Union)
编译后代码 生成对象 完全擦除
反向映射 支持(数字枚举) 不支持
扩展性 可以添加方法 纯粹类型
树摇优化 可能被保留 完全移除
常量值 可以 可以(as const)
运行时访问 可以 不可以
// 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;
// 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"
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"
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 是创建字面量类型的强大工具。

// 不使用 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"
// 常量对象替代字符串枚举
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
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;
  }
}
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 状态码枚举,包含:

  1. 信息响应 (100-199)
  2. 成功响应 (200-299)
  3. 重定向 (300-399)
  4. 客户端错误 (400-499)
  5. 服务器错误 (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

实现一个完整的 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;
  }
}

使用 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"

实现一个类型安全的 API 响应处理器:

// 要求:
// 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 { /* ... */ }

参考答案

// 状态定义
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);

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • typescript/第四章_枚举与字面量类型.txt
  • 最后更改: 2026/03/09 15:23
  • 张叶安