文章目录

TypeScript类型收窄在switch语句中的穷尽性检查实现

发布于 2026-05-06 19:26:19 · 浏览 10 次 · 评论 0 条

在处理复杂的业务逻辑时,通常会使用联合类型来定义状态或形状。如果在 switch 语句中遗漏了某个分支的处理,程序可能会在运行时出现意外行为。TypeScript 的类型系统可以通过“穷尽性检查”在编译阶段就发现这些遗漏。本指南将演示如何利用 never 类型实现这一目标。


第一步:定义基础类型

构建一个包含所有可能状态的联合类型。为了演示效果,定义一个表示网络请求状态的类型 State

type State =
  | { status: 'loading' }
  | { status: 'success'; data: string }
  | { status: 'error'; code: number };

这个类型使用了可辨识联合模式,即每个对象都有一个共同的字面量属性 status,TypeScript 可以据此进行类型收窄。


第二步:编写不安全的 Switch 语句

观察一个普通的 switch 语句处理逻辑。

function getStateMessage(state: State): string {
  switch (state.status) {
    case 'loading':
      return '正在加载中...';
    case 'success':
      return `加载成功: ${state.data}`;
    // 故意遗漏 'error' 分支
    default:
      return '未知状态';
  }
}
```

在这个阶段,TypeScript 编译器**不会报错**,即使你忘记了处理 `error` 状态。函数会返回 `未知状态`,这掩盖了潜在的逻辑错误。

---

### 第三步:实现穷尽性检查

**改造**上述代码,在 `default` 分支中引入 `never` 类型检查机制。

1.  **保留**现有的 `case` 分支。
2.  **修改** `default` 分支,将 `state` 赋值给一个类型为 `never` 的变量。
3.  **返回**该变量或抛出错误。

```typescript
function getSafeStateMessage(state: State): string {
  switch (state.status) {
    case 'loading':
      return '正在加载中...';
    case 'success':
      return `加载成功: ${state.data}`;
    case 'error':
      return `发生错误 (代码: ${state.code})`;
    default:
      // 核心检查逻辑
      const exhaustiveCheck: never = state;
      return exhaustiveCheck;
  }
}
```

此时,代码是**合法**的。因为 TypeScript 判断出在处理完 `loading`、`success` 和 `error` 后,`state` 的剩余类型是 `never`(即不可能存在的类型)。将 `never` 赋值给 `never` 类型是安全的。

---

### 第四步:验证检查效果

**模拟**未来的代码变更。假设团队在 `State` 类型中**添加**了一个新的状态 `pending`,但忘记在 `getSafeStateMessage` 中更新 `switch` 语句。

1.  **修改**类型定义:

```typescript
type State =
  | { status: 'loading' }
  | { status: 'success'; data: string }
  | { status: 'error'; code: number }
  | { status: 'pending' }; // 新增状态
```

2.  **保持**函数 `getSafeStateMessage` 不变。

此时,TypeScript 编译器会**立即报错**:

> Type '{ status: "pending"; }' is not assignable to type 'never'.

**原因分析**:
当所有已知分支都被处理后,`state` 的类型应该被收窄为 `never`。然而,由于新增了 `pending` 且未被处理,`default` 分支中的 `state` 实际上变成了 `{ status: 'pending' }` 类型。试图将这个类型赋值给 `never` 类型违反了类型安全规则,因此编译器强制报错。

---

### 第五步:封装通用检查函数

为了避免在每个函数里都手写 `const exhaustiveCheck: never = state`,**抽取**一个独立的工具函数。

**创建**一个名为 `assertNever` 的函数:

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

**调用**该函数替换原本的赋值逻辑:

```typescript
function getRefactoredStateMessage(state: State): string {
  switch (state.status) {
    case 'loading':
      return '正在加载中...';
    case 'success':
      return `加载成功: ${state.data}`;
    case 'error':
      return `发生错误 (代码: ${state.code})`;
    default:
      return assertNever(state);
  }
}

这种写法不仅利用类型检查提示开发者补充遗漏的分支,而且在开发环境中如果真的触发了未定义的分支,函数会抛出明确的错误信息,便于调试。

评论 (0)

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

扫一扫,手机查看

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