在处理复杂的业务逻辑时,通常会使用联合类型来定义状态或形状。如果在 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);
}
}
这种写法不仅利用类型检查提示开发者补充遗漏的分支,而且在开发环境中如果真的触发了未定义的分支,函数会抛出明确的错误信息,便于调试。

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