TypeScript 字面量类型

156 阅读4分钟

在 TypeScript 的类型系统中,字面量类型(Literal Types)是最精确的类型表示形式,它不仅能指定变量是什么类型,还能指定变量必须是什么具体值。这种精确性为开发带来了强大的类型约束能力。

什么是字面量类型?

字面量类型允许开发者将具体的值直接作为类型使用,而不是范围更广的类型如 stringnumber。TypeScript 支持多种字面量类型:

// 字符串字面量类型
type Direction = "north" | "east" | "south" | "west";

// 数字字面量类型
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

// 布尔字面量类型
type TrueOnly = true;

四种字面量类型详解

graph TD
    A[字面量类型] --> B[字符串字面量]
    A --> C[数字字面量]
    A --> D[布尔字面量]
    A --> E[模版字面量]
    
    B --> F[定义固定字符串集合]
    C --> G[定义固定数值集合]
    D --> H[true/false精确区分]
    E --> I[组合字符串模式]

1. 字符串字面量类型(String Literal Types)

最常用的字面量类型,用于约束变量为特定字符串:

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

function fetchData(url: string, method: HttpMethod) {
  // ...
}

// 正确用法
fetchData("/users", "GET");

// 错误用法
fetchData("/users", "get"); // 错误:'get' 不属于 HttpMethod 类型

2. 数字字面量类型(Numeric Literal Types)

适用于需要精确数值约束的场景:

type Month = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
type Rating = 1 | 2 | 3 | 4 | 5;

function setMonth(month: Month) {
  console.log(`设置月份为:${month}`);
}

setMonth(5); // 正确
setMonth(13); // 错误:13 不在可选范围内

3. 布尔字面量类型(Boolean Literal Types)

精确区分 truefalse,适用于需要严格布尔值的场景:

function enableFeature(isEnabled: true) {
  console.log("功能已启用");
}

function disableFeature(isDisabled: false) {
  console.log("功能已禁用");
}

enableFeature(true);  // 正确
enableFeature(false); // 错误:只能传入 true

disableFeature(false); // 正确
disableFeature(true);  // 错误:只能传入 false

4. 模板字面量类型(Template Literal Types)

TypeScript 4.1+ 引入,提供动态生成字符串字面量类型的能力:

type StatusCode = `HTTP_${200 | 400 | 404 | 500}`;
// "HTTP_200" | "HTTP_400" | "HTTP_404" | "HTTP_500"

type CSSValue = `${number}px` | `${number}em` | `${number}rem`;
// "10px" | "2em" | "1.5rem" 等格式

function setPadding(padding: CSSValue) {
  // ...
}

setPadding("15px");   // 正确
setPadding("2.5rem"); // 正确
setPadding("10vh");   // 错误:'vh' 不被支持

字面量类型的核心应用场景

graph TD
    J[主要应用] --> K[Discriminated Unions]
    J --> L[函数重载]
    J --> M[配置对象]
    J --> N[API响应处理]   

1. 可辨识联合(Discriminated Unions)

通过共享字面量类型实现类型安全的联合类型:

type SuccessResponse = {
  status: "success";
  data: object;
};

type ErrorResponse = {
  status: "error";
  errorCode: number;
  message: string;
};

type ApiResponse = SuccessResponse | ErrorResponse;

function handleResponse(response: ApiResponse) {
  // 根据字面量类型进行区分
  if (response.status === "success") {
    console.log("数据:", response.data);
  } else {
    console.error(`错误 ${response.errorCode}: ${response.message}`);
  }
}

2. 类型安全的配置对象

避免魔法字符串问题:

type Theme = "dark" | "light" | "system";
type FontSize = "small" | "medium" | "large";

interface AppConfig {
  theme: Theme;
  fontSize: FontSize;
  animate: boolean;
}

function createApp(config: AppConfig) {
  // 使用安全配置
}

// 正确配置
createApp({
  theme: "dark",
  fontSize: "medium",
  animate: true
});

// 错误示范
createApp({
  theme: "dark-mode", // 错误:不在 Theme 选项中
  fontSize: "medium",
  animate: true
});

3. 函数重载与参数验证

type Operation = "add" | "subtract" | "multiply" | "divide";

function calculate(operation: Operation, a: number, b: number): number {
  switch (operation) {
    case "add": return a + b;
    case "subtract": return a - b;
    case "multiply": return a * b;
    case "divide": return a / b;
    default: 
      // 由于TypeScript的类型保护,default分支实际上不会执行
      const _exhaustiveCheck: never = operation;
      return _exhaustiveCheck;
  }
}

console.log(calculate("add", 5, 3)); // 8
console.log(calculate("power", 2, 3)); // 错误:'power' 不在选项中

4. API端点与路由安全

type ApiEndpoint = 
  | "/user/login"
  | "/user/profile"
  | "/posts/all"
  | `/posts/${number}`;

function fetchApi(endpoint: ApiEndpoint) {
  // ...
}

fetchApi("/user/profile");      // 正确
fetchApi("/posts/42");          // 正确
fetchApi("/comments/recent");   // 错误:不在允许的端点中

高级技巧

graph TD
O[高级技巧] --> P[const断言]
    O --> Q[类型收缩]
    O --> R[模版字面量类型]
    O --> S[实用类型操作]

1. const 断言固定变量类型

使用 as const 将变量声明为不可变字面量类型:

// 普通数组类型:(string | number)[]
const mixedValues = ["admin", "user", 1]; 

// 字面量类型数组:readonly ["admin", "user", 1]
const userRoles = ["admin", "user", 1] as const; 

// 自动推导为 "admin" 类型,而不是 string
const adminRole = userRoles[0]; 

2. 类型收缩与类型守卫

在条件语句中自动收缩类型范围:

function processMessage(message: string | boolean) {
  if (message === true) {
    // 此处 message 为 true 类型
    console.log("真值消息");
  } else if (message === false) {
    // 此处 message 为 false 类型
    console.log("假值消息");
  } else {
    // 此处 message 为 string 类型
    console.log(`字符串消息:${message}`);
  }
}

3. 实用类型操作

使用内置类型操作符处理字面量类型:

// 从对象提取字面量类型
const statuses = {
  IDLE: "idle",
  LOADING: "loading",
  SUCCESS: "success",
  ERROR: "error"
} as const;

type Status = keyof typeof statuses; 
// "IDLE" | "LOADING" | "SUCCESS" | "ERROR"

// 使用模板字面量重写键名
type EventTypes = "click" | "hover" | "focus";
type EventHandlers = `on${Capitalize<EventTypes>}`;
// "onClick" | "onHover" | "onFocus"

// 从字面量类型移除前缀
type EventType = Uncapitalize<RemovePrefix<EventHandlers, 'on'>>;
// "click" | "hover" | "focus"

// 辅助类型:移除前缀
type RemovePrefix<T extends string, P extends string> = 
  T extends `${P}${infer Rest}` ? Rest : T;

4. 复杂模板字面量模式

创建高级字符串验证规则:

// Email验证类型简例
type EmailLocalPart = `${string}@${string}.${string}`;
type Email = `${string}@${string}.${"com" | "org" | "net"}`;

// 路由参数提取
type RouteParams<T extends string> = 
  T extends `${string}/:${infer Param}/${infer Rest}` 
    ? Param | RouteParams<`/${Rest}`> 
    : T extends `${string}/:${infer Param}` 
      ? Param 
      : never;

type Params = RouteParams<"/user/:userId/post/:postId">;
// "userId" | "postId"

// CSS选择器验证
type CSSSelector = 
  | ElementSelector
  | ClassSelector
  | IdSelector
  | AttributeSelector;

type ElementSelector = `${string}`;
type ClassSelector = `.${string}`;
type IdSelector = `#${string}`;
type AttributeSelector = `[${string}]`;

字面量类型 VS 枚举类型

特性字面量联合类型枚举类型
类型定义type Size = 'S' | 'M' | 'L'enum Size { S, M, L }
运行时值保留原始值生成额外运行时代码
反向映射不支持支持
字符串值枚举原生支持需要显示设置 Size.S = 'S'
编译后大小无额外代码生成额外运行时代码
树摇优化 (Tree Shaking)支持良好完全支持需要额外配置
复杂值支持支持数字、布尔值、字符串仅支持数值和字符串

推荐选择:

  1. 使用字符串字面量联合类型处理简单选项值
  2. 仅当需要反向映射时才使用枚举类型
  3. 对于外部API合约,考虑使用 as const 对象实现类似枚举的结构:
const Size = {
  SMALL: "S",
  MEDIUM: "M",
  LARGE: "L"
} as const;

type Size = typeof Size[keyof typeof Size]; // "S" | "M" | "L"

案例分析:实现类型安全的国际化

// 1. 定义支持的语言
type Language = "en" | "es" | "fr" | "de";

// 2. 定义所有可能的消息键
type MessageKey = "welcome" | "goodbye" | "warning";

// 3. 定义消息结构
type Messages = {
  [lang in Language]: {
    [key in MessageKey]: string;
  };
};

// 4. 实现消息字典
const messages: Messages = {
  en: {
    welcome: "Welcome!",
    goodbye: "Goodbye!",
    warning: "Warning!"
  },
  es: {
    welcome: "¡Bienvenido!",
    goodbye: "¡Adiós!",
    warning: "¡Advertencia!"
  },
  fr: {
    welcome: "Bienvenue!",
    goodbye: "Au revoir!",
    warning: "Avertissement!"
  },
  de: {
    welcome: "Willkommen!",
    goodbye: "Auf Wiedersehen!",
    warning: "Warnung!"
  }
};

// 5. 类型安全的翻译函数
function translate(lang: Language, key: MessageKey): string {
  return messages[lang][key];
}

// 使用
console.log(translate("es", "welcome")); // "¡Bienvenido!"
console.log(translate("jp", "welcome")); // 错误:"jp" 不在 Language 中
console.log(translate("en", "intro"));   // 错误:"intro" 不在 MessageKey 中

为什么使用字面量类型?

  1. 提高类型安全性:消除魔法字符串/数字,防止无效值
  2. 提升代码可读性:明确表达允许的值集合
  3. 增强IDE支持:提供精确的自动补全提示
  4. 改善重构能力:类型检查保证值改变时的正确性
  5. 精确错误定位:在编译时而非运行时发现问题

当与 TypeScript 的其他高级特性(如联合类型、类型守卫和类型推断)结合使用时,字面量类型成为创建精确、自描述和类型安全代码的不可缺少的工具。