POST://typescript-discriminated-unions
带点橘子味的馒头的头像
带点橘子味的馒头

FRONTEND / DESKTOP DEV

返回文章列表

TypeScript 判别联合:让"不可能状态"在编译期就报错

2 分钟· 754 2 次阅读

很多 bug 来自"本不该出现的状态":加载中却拿到了数据、失败了却显示成功。TypeScript 的**判别联合(Discriminated Union)**能把这些矛盾在编译期就挡下来。

什么是判别联合

给一组联合类型加一个共同的字面量字段(叫"判别式"),TS 就能据此自动收窄类型:

type Result =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: string }
  | { status: "error"; message: string };

注意 successdataerrormessage,而 idle / loading 没有。这就是关键——额外的字段跟着判别式走

配合 switch 自动收窄

function render(r: Result): string {
  switch (r.status) {
    case "idle":
      return "尚未开始";
    case "loading":
      return "加载中…";
    case "success":
      return r.data.toUpperCase(); // ✅ 这里 r 被收窄成 success,data 一定存在
    case "error":
      return `出错:${r.message}`; // ✅ 这里 r 是 error,message 一定存在
  }
}

case "success" 分支里,TS 已经知道 r 是那个带 data 的变体,直接访问 r.data 不会报错。这就是"类型跟着判别式走"的威力。

穷尽检查:漏一个分支就编译报错

最怕的是后来加了一个状态,却忘了在 switch 里处理。用 never穷尽性检查

function assertNever(x: never): never {
  throw new Error("未处理的分支:" + JSON.stringify(x));
}

function render(r: Result): string {
  switch (r.status) {
    case "idle": return "尚未开始";
    case "loading": return "加载中…";
    case "success": return r.data.toUpperCase();
    case "error": return `出错:${r.message}`;
    default:
      return assertNever(r); // 若新增 status 忘了处理,这里 r 不再是 never → 编译报错
  }
}

当你给 Result 新增 { status: "retry" } 却没加对应 case,最后一行的 r 就不再是 never,TS 立刻红给你看。这是状态机最稳的护栏。

收窄的几种常用手段

除了 switch,日常收窄还有这些:

写法 适用场景
typeof x === "string" 区分基本类型
x in obj 判断某个属性是否存在
Array.isArray(x) 数组收窄
x instanceof Foo 类实例收窄
判别联合 + switch 多状态、带附加字段的对象

真实例子:处理 API 响应

判别联合特别适合表达"一次请求的所有可能结果":

type ApiResponse<T> =
  | { ok: true; code: 200; data: T }
  | { ok: false; code: 404; error: "not_found" }
  | { ok: false; code: 500; error: "server" };

function handle<T>(res: ApiResponse<T>) {
  if (res.ok) {
    return res.data; // ✅ 只有 ok:true 分支能拿到 data
  }
  // 这里 res 必然是 ok:false,error 字段一定存在
  console.error(res.code, res.error);
}

调用方永远不必写 res.data && res.ok 这种防御式判断——类型系统已经保证 data 只在 ok:true 时出现。

小结

判别联合的三步心法:

  1. 加一个字面量判别式字段(如 status / ok),让联合类型的每个变体可被区分;
  2. switch + never 做穷尽检查,新增状态忘了处理就编译报错;
  3. 把"不可能状态"交给类型系统,少写运行时防御代码,也更不容易出错。

它特别适合状态机、API 响应、表单步骤这类"有限几种情况"的场景——用好了,很多 undefined is not an object 会在你写代码的那一刻就消失。

相关文章