【TypeScript】Discriminated Unions 详解

TypeScript 是一种强类型的 JavaScript 超集,提供了丰富的类型系统以帮助开发者编写更安全、更健壮的代码。在 TypeScript 的类型系统中,discriminated unions(区别联合类型)是一个强大的工具,用于处理不同类型的数据结构,尤其适用于当我们处理具有类似形状但包含不同属性的复杂结构时。

一、什么是 Discriminated Unions?

discriminated unions 是 TypeScript 提供的一种高级类型,它允许通过公共属性来区分联合类型中的不同成员。这种技术通常应用于处理那些在特定属性上有明确区别的数据类型。例如,当我们处理几何图形时,不同的图形类型可能会共享某些相同的属性(如 kind),但具体的属性值和方法逻辑则因图形类型而异。

为了更好地理解 discriminated unions,我们以几何图形为例,假设我们要定义圆形和方形这两种形状。圆形需要存储其半径(radius),方形则需要存储其边长(sideLength)。在 TypeScript 中,我们可以通过一个公共属性 kind 来区分它们。

1. 初步定义 Shape 接口

我们先尝试定义一个 Shape 接口,将圆形和方形统一在一起:

interface Shape {
  kind: "circle" | "square";
  radius?: number;
  sideLength?: number;
}

在上面的代码中,我们通过 kind 属性使用了一个字符串字面量联合类型 "circle" | "square",用于表示形状的类型。在处理形状时,kind 可以帮助我们判断当前是圆形还是方形。

但是,当我们试图通过这种方式来编写业务逻辑时,可能会遇到一些问题。让我们看一个具体的例子:

function getArea(shape: Shape): number {
  return Math.PI * shape.radius ** 2;
}

此时 TypeScript 会提示错误:'shape.radius' is possibly 'undefined',因为 radius 在方形中是未定义的。这就是我们目前的定义方式所带来的问题:尽管 radiussideLength 是各自形状专属的属性,但 TypeScript 并不知道这些属性之间的约束关系。

2. 改进 Shape 的定义

为了解决这个问题,我们可以将 Shape 拆分为更具体的类型,并且明确每种类型的属性。

interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

在这个定义中,CircleSquare 成为了两个独立的接口,它们的属性是完全根据形状类型定义的。在 Shape 联合类型中,我们通过 kind 属性区分了不同的形状。

3. 处理 Shape 的逻辑

现在我们来看看如何在 getArea 函数中根据 kind 属性来正确处理不同的形状:

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    default:
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

在上面的代码中,我们使用了 switch 语句来根据 shape.kind 进行分支处理。当 kind"circle" 时,我们知道 shape 的类型是 Circle,因此可以安全地访问 radius 属性;当 kind"square" 时,我们则访问 sideLength 属性。

这里的关键是 TypeScript 的类型收窄(narrowing)功能。在每个 case 分支中,TypeScript 能够推断出 shape 的具体类型,从而允许我们访问相应的属性。

二、Discriminated Unions 的优势

通过 discriminated unions,TypeScript 可以帮助我们显著提高代码的健壮性和可维护性。下面是它的几个主要优势:

1. 更精确的类型推断

在使用 discriminated unions 时,TypeScript 可以根据上下文自动推断变量的具体类型。这样可以减少手动类型断言的需要,并防止类型错误。例如,当我们检查 kind 是否为 "circle" 时,TypeScript 就会将 shape 类型自动收窄为 Circle

2. 避免错误的类型访问

由于 TypeScript 会根据类型推断防止我们访问不存在的属性,因此可以有效避免类似于访问方形的 radius 或访问圆形的 sideLength 这样的逻辑错误。

3. 使用 never 类型进行穷尽检查

在上面的 getArea 示例中,我们引入了 never 类型进行穷尽性检查never 类型表示不可能存在的类型。在 switch 语句的 default 分支中,我们使用 never 来确保所有可能的 kind 值都被处理。如果以后新增了一个类型(例如 Triangle),TypeScript 会在我们忘记处理新类型时发出警告。

interface Triangle {
  kind: "triangle";
  sideLength: number;
}

type Shape = Circle | Square | Triangle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
    default:
      const _exhaustiveCheck: never = shape;
      return _exhaustiveCheck;
  }
}

如果忘记处理 Triangle,TypeScript 会报错:Type 'Triangle' is not assignable to type 'never',从而提醒我们需要在代码中考虑到所有可能的类型。

三、应用场景

1. 处理不同类型的消息

discriminated unions 常用于处理消息传递系统,例如客户端和服务器之间的通信。每种消息类型可以有不同的属性,但我们可以通过 kind 或类似的字段来区分它们。

interface SuccessMessage {
  kind: "success";
  code: number;
}

interface ErrorMessage {
  kind: "error";
  message: string;
}

type Message = SuccessMessage | ErrorMessage;

function handleMessage(msg: Message) {
  switch (msg.kind) {
    case "success":
      console.log(`Success with code: ${msg.code}`);
      break;
    case "error":
      console.error(`Error: ${msg.message}`);
      break;
  }
}

2. 状态管理

在状态管理框架(如 Redux)中,discriminated unions 也非常有用。例如,应用程序中的不同状态(加载、成功、失败等)可以使用 discriminated unions 进行建模,从而确保处理每种状态的逻辑都得到充分的考虑。

interface LoadingState {
  kind: "loading";
}

interface SuccessState {
  kind: "success";
  data: any;
}

interface ErrorState {
  kind: "error";
  error: string;
}

type State = LoadingState | SuccessState | ErrorState;

function render(state: State) {
  switch (state.kind) {
    case "loading":
      console.log("Loading...");
      break;
    case "success":
      console.log("Data loaded: ", state.data);
      break;
    case "error":
      console.error("Error: ", state.error);
      break;
  }
}

四、总结

discriminated unions 是 TypeScript 中处理复杂数据结构的强大工具,特别适用于需要根据某个公共属性来区分不同类型的场景。通过正确地使用这种类型系统特性,我们可以编写更安全、更具可读性的代码,并且在开发过程中能够得到更多的类型检查和错误提示。

推荐:


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter-Lu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值