TypeScript 类型守卫用于判别联合类型(可扩展代码的最佳实践)

发布: (2026年3月15日 GMT+8 01:27)
8 分钟阅读
原文: Dev.to

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 = { /* ... */ };

这使代码保持可维护

常见的开发者错误

  1. 使用 any

    function handle(res: any) { /* ... */ }

    为什么不好: 删除了 TypeScript 的安全性。

  2. 忘记穷尽检查

    开发者经常忘记处理 新的联合类型情况
    始终使用 assertNever 辅助函数:

    function assertNever(x: never): never {
      throw new Error(`Unexpected object: ${x}`);
    }
  3. 混合不相关的联合类型

    type Result =
      | { type: "user" }
      | { type: "product" }
      | { type: "error" };

    为什么不好: 将不相关的领域关注点混在一起。
    解决方案: 将它们拆分为独立的类型。

为什么判别联合如此强大

它们帮助你:

  • 防止不可能的状态
  • 编写自解释代码
  • 在编译时捕获错误
  • 提高大型代码库的可维护性

判别联合被广泛用于:

  • Angular 状态管理
  • Redux / NgRx
  • API 响应建模
  • 领域驱动设计

Final Thoughts

Discriminated unions + type guards 是 TypeScript 中最强大的模式之一。

它们让你能够 安全地建模真实世界的状态转换,同时保持代码的 可读性和可扩展性

如果你正在构建 大型 TypeScript 应用程序,掌握此模式将显著提升你的 代码质量和可靠性

0 浏览
Back to Blog

相关文章

阅读更多 »

GPU Flight — 系统架构

GPU Flight 架构概述 上一篇文章讨论了 SASS 级别的线程分歧。在深入其他优化策略之前,先回顾一下会有帮助。