文章目录

TypeScript联合类型在switch语句中的穷尽性检查

发布于 2026-04-20 09:22:06 · 浏览 4 次 · 评论 0 条

TypeScript联合类型在switch语句中的穷尽性检查

在开发中,我们经常需要根据一个变量的不同取值(即联合类型)执行不同的逻辑。使用 switch 语句时,如果忘记处理某种情况,程序可能会在运行时产生不可预知的行为。TypeScript 提供了一种利用 never 类型进行“穷尽性检查”的技巧,可以在编译阶段就发现这种遗漏。


1. 定义基础场景与问题

定义 一个表示状态的联合类型 State,包含 PendingSuccessFail 三种状态。

type State = 'Pending' | 'Success' | 'Fail';

编写 一个处理该状态的函数 processState,接收 state 参数。

function processState(state: State): string {
  switch (state) {
    case 'Pending':
      return '处理中...';
    case 'Success':
      return '操作成功';
    // 注意:这里故意遗漏了 'Fail' 的情况
  }
}

此时,TypeScript 编译器可能不会报错,函数隐式返回了 undefined。如果在后续代码中强依赖返回的字符串,就会导致 Bug。


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

添加 一个 default 分支,并将 state 赋值给一个类型为 never 的常量。

function processState(state: State): string {
  switch (state) {
    case 'Pending':
      return '处理中...';
    case 'Success':
      return '操作成功';
    case 'Fail':
      return '操作失败';
    default:
      // 核心检查逻辑
      const exhaustiveCheck: never = state;
      return exhaustiveCheck;
  }
}

理解 其背后的原理:

TypeScript 的类型控制流分析会根据 switchcase 分支缩小 state 的类型范围。

  1. 当所有 casePending, Success, Fail)都被处理后,进入 default 分支时,TypeScript 判断 state 已经没有可能的剩余值,因此它的类型被缩小为 never(即“不可能存在”的类型)。
  2. state 赋值给 never 类型的 exhaustiveCheck 是合法的。

如果此时我们修改 State 类型,添加一个新的状态 'Unknown'

type State = 'Pending' | 'Success' | 'Fail' | 'Unknown';

TypeScript 会立即在 default 分支报错:

Type 'string' is not assignable to type 'never'.

这是因为 switch 没有处理 'Unknown',所以进入 default 时,state 的类型变成了 'Unknown',而 'Unknown' 无法赋值给 never。这个错误强制开发者修正代码,补全 case 分支。


3. 提取为辅助函数(最佳实践)

为了在多个地方复用该逻辑,避免每次都手写 const check: never = ...,我们可以封装一个辅助函数。

创建 一个名为 assertNever 的函数:

function assertNever(x: never): never {
  throw new Error('Unexpected object: ' + x);
}

重构 processState 函数,在 default 中调用它:

function processState(state: State): string {
  switch (state) {
    case 'Pending':
      return '处理中...';
    case 'Success':
      return '操作成功';
    case 'Fail':
      return '操作失败';
    default:
      return assertNever(state);
  }
}

当再次发生类型遗漏时,TypeScript 同样会报错。因为 assertNever 的参数要求是 never,而实际的 state 包含未处理的枚举值,类型不匹配。


4. 类型检查流程解析

为了更直观地理解 TypeScript 如何在代码执行流中判断类型,请参考以下逻辑流程:

graph TD A["输入变量: type State = 'A' | 'B'"] --> B{Switch Case
匹配 'A'?} B -- 是 --> C["执行逻辑 A\n剩余类型: 'B'"] B -- 否 --> C C --> D{Switch Case
匹配 'B'?} D -- 是 --> E["执行逻辑 B\n剩余类型: never"] D -- 否 --> E E --> F["Default 分支赋值"] F --> G{剩余类型是否为 never?} G -- 是 --> H["编译通过"] G -- 否 --> I["编译报错\n类型不匹配"]

5. 处理对象类型的联合

这种技巧同样适用于对象类型的联合,而不仅是字符串字面量。

定义 一个 Shape 联合类型,包含 CircleSquare

type Circle = {
  kind: 'circle';
  radius: number;
};

type Square = {
  kind: 'square';
  sideLength: number;
};

type Shape = Circle | Square;

编写 计算面积的函数,并应用穷尽性检查:

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;
  }
}

如果未来添加一个新的形状 TriangleShape 类型中:

type Triangle = {
  kind: 'triangle';
  base: number;
  height: number;
};

type Shape = Circle | Square | Triangle;

getArea 函数中的 default 分支会立即报错,提示你处理 Triangle 的逻辑。


6. 方案对比总结

检查方式 实现难度 报错时机 可维护性
人工检查 运行时(可能导致错误) 差,易遗漏
ESLint 规则 中(需配置) 编译时(Lint 阶段) 中,依赖团队配置
never 类型检查 编译时(类型检查阶段) 优,原生支持,自动触发

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文