讲解

联合类型(union)用竖线连接多个类型,表示「是其中之一」:string | number 表示可以是字符串也可以是数字。它是 TypeScript 表达现实业务的主力工具——接口返回可能成功也可能失败、字段可空、状态有限几种,都是联合类型的用武之地。对联合类型的值做操作,编译器只允许调用所有成员共有的方法;想用某个成员特有的方法,必须先收窄(下一章专题)。

字面量类型(literal type)是把类型精确到某个具体的值:"draft" 是一个类型,表示「只能是字符串 draft」。单个字面量类型意义不大,但把几个字面量联合起来的 "draft" | "published" | "archived" 就是状态机的完美表达——比 string 精确得多,多一个字母都编译不过。数字和布尔也有字面量类型,1 | 2 | 3、true 都是合法写法。

与字面量类型紧密相关的是 const 断言和 let/const 的推断差异:const name = "ts" 推断为字面量类型 "ts"(因为不可再赋值),let name = "ts" 推断为 string(因为还能改)。对象和数组的字面量默认会被「拓宽」(属性推断为 string 而非字面量),想保留字面量精度就用 as const——整个对象变深只读且每个值都是字面量类型。as const 在配置表、常量枚举场景极其常用。

示例

联合类型与共有成员的限制:

function formatId(id: string | number): string {
  // 只能调用 string 和 number 共有的方法
  return `ID-${id.toString()}`;
}

console.log(formatId("abc"));
console.log(formatId(1024));

字面量联合表达有限状态:

type OrderStatus = "pending" | "paid" | "shipped" | "done";

function nextStatus(s: OrderStatus): OrderStatus {
  switch (s) {
    case "pending":
      return "paid";
    case "paid":
      return "shipped";
    default:
      return "done";
  }
}

let current: OrderStatus = "pending";
current = nextStatus(current);
// current = "cancelled"; // 若取消注释:不在联合之内,编译报错
console.log(`当前状态:${current}`);

as const 保留字面量精度:

const CONFIG = {
  env: "prod",
  retries: 3,
} as const;

// CONFIG.env = "dev"; // 若取消注释:深只读,编译报错
type Env = typeof CONFIG.env; // 类型是 "prod" 而不是 string

function describe(env: "dev" | "prod"): string {
  return env === "prod" ? "生产环境" : "开发环境";
}

console.log(describe(CONFIG.env), `重试 ${CONFIG.retries} 次`);

常见坑

  • 用 string 代替字面量联合:status: string 让 "paied" 这种拼写错误一路漏到运行时;能枚举的值就用字面量联合。
  • 对联合类型直接用成员特有方法:value.toUpperCase() 在 string | number 上报错——先收窄(typeof 判断),下一章细讲。
  • 忘记 as const 导致类型被拓宽:想拿 "a" | "b" 却写成了普通数组,得到的是 string[];常量表一律 as const。
  • 字面量联合与 enum 纠结:简单状态集合优先字面量联合(零运行时开销),枚举章节会对比两者取舍。

小结

联合类型表达「之一」;字面量类型精确到具体值,联合后是状态/选项的最佳表达;as const 冻结推断精度。下一章讲如何让编译器在分支里自动认清具体成员——类型收窄。