TypeScript 类型守卫用于判别联合类型(可扩展代码的最佳实践)
I’m happy to translate the article for you, but I’ll need the full text of the article (the content you’d like translated). Could you please paste the article’s body here? Once I have that, I’ll provide a Simplified‑Chinese translation while preserving the original formatting, markdown, and code blocks.
本指南将涵盖
- 什么是判别联合
- 为什么类型守卫很重要
- 在实际应用中使用它们的最佳实践
- 开发者常犯的错误
问题:处理多种数据形状
在许多应用中,一个变量可以表示 不同类型的对象(例如,API 响应状态)。
type ApiResponse =
| { status: "loading" }
| { status: "success"; data: string[] }
| { status: "error"; message: string };这是一种 联合类型。
但是 TypeScript 如何知道哪些属性是存在的呢?
function handleResponse(res: ApiResponse) {
console.log(res.data);
}TypeScript 错误
Property 'data' does not exist on type 'ApiResponse'.因为 并非每个联合成员都有 data。
Solution: Discriminated Unions
A 判别联合 使用 公共属性(判别器)来标识类型。在我们的示例中,判别器是 status。
现在 TypeScript 可以安全地进行类型收窄:
function handleResponse(res: ApiResponse) {
if (res.status === "success") {
console.log(res.data);
}
}TypeScript 会自动知道在 if 块内部 res 的类型是:
{ status: "success"; data: string[] }这称为 类型收窄。
什么是类型守卫?
type guard 是帮助 TypeScript 确定精确类型的逻辑。
if (res.status === "error") { /* … */ }该条件充当 type guard。
我们也可以创建 custom type guards。
创建自定义类型守卫
自定义守卫提升 可读性和可复用性。
function isSuccess(
res: ApiResponse
): res is { status: "success"; data: string[] } {
return res.status === "success";
}用法
if (isSuccess(res)) {
console.log(res.data); // `res` 会自动被缩小类型
}这在 大型应用程序 中非常有用。
真实案例:支付系统
考虑一个响应可能不同的支付系统。
type PaymentResult =
| { type: "success"; transactionId: string }
| { type: "failed"; error: string }
| { type: "pending"; estimatedTime: number };使用带有判别属性的联合类型:
function handlePayment(result: PaymentResult) {
switch (result.type) {
case "success":
console.log(result.transactionId);
break;
case "failed":
console.log(result.error);
break;
case "pending":
console.log(result.estimatedTime);
break;
}
}字段 type 充当 判别属性。
最佳实践 1:始终使用单一判别属性
常用判别属性名称:
type
kind
status
variant示例
type Shape =
| { type: "circle"; radius: number }
| { type: "square"; size: number };避免使用多个判别属性,例如 { kind: "circle", shape: "circle" }。
坚持使用 单一明确的属性。
最佳实践 2:使用 switch 替代多个 if 语句
switch 语句提升 可读性和可维护性。
不佳
if (shape.type === "circle") {}
if (shape.type === "square") {}更好
switch (shape.type) {
case "circle":
// …
break;
case "square":
// …
break;
}这也能实现 穷尽类型检查。
最佳实践 3:使用穷尽检查
一种强大的 TypeScript 技巧。
function assertNever(x: never): never {
throw new Error("Unexpected type");
}示例
switch (shape.type) {
case "circle":
// …
break;
case "square":
// …
break;
default:
assertNever(shape);
}如果添加了新类型(例如 { type: "triangle" }),TypeScript 会立即抛出错误,防止静默的 bug。
最佳实践 4:避免在联合类型中使用可选字段
不良设计
type ApiResponse = {
status: "success" | "error";
data?: string[];
error?: string;
};这会产生 不明确的状态。
更佳写法
type ApiResponse =
| { status: "success"; data: string[] }
| { status: "error"; error: string };现在类型系统只会强制 有效状态。
最佳实践 5:为 UI 状态使用判别联合
type LoadingState =
| { status: "idle" }
| { status: "loading" }
| { status: "error"; message: string };最佳实践 5 – 为 UI 状态使用判别联合
type ApiResponse =
| { status: "loading" }
| { status: "success"; data: User[] }
| { status: "error"; message: string };在 UI 中的使用
switch (state.status) {
case "loading":
return "Loading...";
case "success":
return state.data;
case "error":
return state.message;
}这可以防止 无效的 UI 状态。
最佳实践 6 – 创建可重用的类型守卫
不要在各处重复逻辑,定义一次守卫后即可重复使用。
示例
function isError(res: ApiResponse): res is { status: "error"; message: string } {
return res.status === "error";
}用法
if (isError(response)) {
console.log(response.message);
}可重用的守卫改进 clean architecture。
最佳实践 7 – 保持联合类型小且专注
避免极其庞大的联合(例如,“50 种不同的变体”)。
将其拆分为逻辑组。
示例
// Separate concerns
type UserState = { /* ... */ };
type OrderState = { /* ... */ };
type PaymentState = { /* ... */ };这使代码保持可维护。
常见的开发者错误
使用
anyfunction handle(res: any) { /* ... */ }为什么不好: 删除了 TypeScript 的安全性。
忘记穷尽检查
开发者经常忘记处理 新的联合类型情况。
始终使用assertNever辅助函数:function assertNever(x: never): never { throw new Error(`Unexpected object: ${x}`); }混合不相关的联合类型
type Result = | { type: "user" } | { type: "product" } | { type: "error" };为什么不好: 将不相关的领域关注点混在一起。
解决方案: 将它们拆分为独立的类型。
为什么判别联合如此强大
它们帮助你:
- 防止不可能的状态
- 编写自解释代码
- 在编译时捕获错误
- 提高大型代码库的可维护性
判别联合被广泛用于:
- Angular 状态管理
- Redux / NgRx
- API 响应建模
- 领域驱动设计
Final Thoughts
Discriminated unions + type guards 是 TypeScript 中最强大的模式之一。
它们让你能够 安全地建模真实世界的状态转换,同时保持代码的 可读性和可扩展性。
如果你正在构建 大型 TypeScript 应用程序,掌握此模式将显著提升你的 代码质量和可靠性。