TypeScript 判别联合:让"不可能状态"在编译期就报错
很多 bug 来自"本不该出现的状态":加载中却拿到了数据、失败了却显示成功。TypeScript 的**判别联合(Discriminated Union)**能把这些矛盾在编译期就挡下来。
什么是判别联合
给一组联合类型加一个共同的字面量字段(叫"判别式"),TS 就能据此自动收窄类型:
type Result =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error"; message: string };
注意 success 带 data、error 带 message,而 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 时出现。
小结
判别联合的三步心法:
- 加一个字面量判别式字段(如
status/ok),让联合类型的每个变体可被区分; - 用
switch+never做穷尽检查,新增状态忘了处理就编译报错; - 把"不可能状态"交给类型系统,少写运行时防御代码,也更不容易出错。
它特别适合状态机、API 响应、表单步骤这类"有限几种情况"的场景——用好了,很多 undefined is not an object 会在你写代码的那一刻就消失。