Kodokon kodokon.com

字面量类型与可辨识联合

用编译器替你检查的联合类型来建模应用程序的各种状态。

8 分钟 · 3 题

在 Kodokon 中打开本课

字面量类型把一个值限制为确切的内容:是 'info' 而不是 string。它是那些无法被误用的 API 的基石:编译器会拒绝列表之外的任何值,而你的编辑器会提供自动补全。

TYPESCRIPT
type Level = "info" | "warn" | "error";

function log(level: Level, message: string): void {
  console.log(`[${level}] ${message}`);
}

log("info", "Server started");
// log("fatal", "Boom"); -> rejected by the compiler
字面量的联合锁定了允许的取值

下一步是组合那些共享一个公共字面量属性的对象类型,这个属性被称为辨识标记。举例来说,网络请求的每一种状态只携带属于它自己的数据:在加载中时访问 data 根本就是不可能的。

TYPESCRIPT
type ApiState =
  | { status: "loading" }
  | { status: "success"; data: string[] }
  | { status: "error"; message: string };

function render(state: ApiState): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return state.data.join(", ");
    case "error":
      return state.message;
  }
}
由 status 属性辨识的联合

在每个 case 中,TypeScript 都会收窄类型:在 case 'error' 之后,state.message 存在而 state.data 不存在。这个被称为类型收窄(narrowing)的机制取代了脆弱的手动检查。再加上一个穷尽性守卫,把整件事彻底锁定。

TYPESCRIPT
function assertNever(value: never): never {
  throw new Error("Unhandled case: " + String(value));
}

function label(state: ApiState): string {
  switch (state.status) {
    case "loading": return "In progress";
    case "success": return "Done";
    case "error": return "Failed";
    default: return assertNever(state);
  }
}
用 never 类型实现穷尽性守卫

知识检测

确认你已牢记本课的重点内容。

  1. 在可辨识联合中,像 status 这样的共享属性起什么作用?
    • 提升运行时性能
    • 让编译器在每个分支中收窄类型
    • 使该属性在接收到的 JSON 中成为必填项
    • 防止属性重复
  2. 对于 type Level = 'info' | 'warn',哪个对 log(level, msg) 的调用能通过编译?
    • log('debug', msg)
    • log('info', msg)
    • log(someString, msg),其中 someString 是 string 类型
  3. 在 switch 的 default 分支中放一个 assertNever(value: never) 函数有什么用?
    • 在生产环境记录错误
    • 把遗漏的 case 变成编译错误
    • 把值转换为 never
    • 让程序更快地终止