在 TypeScript 的类型系统中,never 类型常常被忽略或误解,但它实际上是一个强大而精妙的设计。作为类型空间的底层类型,never 代表的是"永远不会发生"的值,它在类型安全和高级类型操作中扮演着关键角色。本文将全面剖析 never 类型的本质和应用场景。
什么是 Never 类型?
基本定义
never 类型表示的是那些永远不可能存在的值的类型。更准确地说:
- 它是任何类型都无法赋值给的底层类型
- 它表示函数永远不会正常返回(抛出异常或陷入死循环)
- 在联合类型中,
never类型会自动消失 - 在交叉类型中,
never会覆盖所有其他类型
// never 类型的基本特征
type Example = never; // 无法被赋任何值
// 所有类型都不能赋值给 never
let impossible: never;
impossible = "test"; // ❌ 错误:不能将类型"string"分配给类型"never"
never vs void vs unknown
| 特性 | never | void | unknown |
|---|---|---|---|
| 值分配 | 无值可分配 | 只能分配 undefined/null | 所有值可分配 |
| 函数返回值 | 永不返回的函数 | 无返回值的函数 | 不应用于函数返回值 |
| 类型收窄 | 表示不可能情况 | 无特殊作用 | 需要类型收窄 |
| 类型操作中 | 从联合类型消失 | 保留在联合类型中 | 在联合类型中覆盖其他类型 |
| 赋值给其他 | 可赋值给任何类型 | 需要配置严格空值检查 | 不能直接赋值给其他类型 |
为什么需要 Never 类型?
1. 表示不可能发生的情况
never 类型用于标记在程序控制流中不应该到达的代码路径。这有助于捕获逻辑错误:
type Shape = "circle" | "square" | "triangle";
function calculateArea(shape: Shape): number {
switch(shape) {
case "circle":
return Math.PI * radius ** 2;
case "square":
return sideLength ** 2;
default:
// 此时,类型已被收窄为 'never'
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
在上述代码中,如果未来添加了新形状但忘记更新 calculateArea 函数:
// 新增 "pentagon" 形状
type Shape = "circle" | "square" | "triangle" | "pentagon";
function calculateArea(shape: Shape): number {
switch(shape) {
// ...原有实现
default:
const _exhaustiveCheck: never = shape; // ❌ 错误!
// Type '"pentagon"' is not assignable to type 'never'
return _exhaustiveCheck;
}
}
编译器会立即提醒我们处理新增的形状,这是强大的穷尽性检查机制。
2. 标识特殊函数类型
当函数永远不会正常返回时(抛出异常或无限循环),使用 never 作为返回类型:
// 抛出错误的函数
function panic(message: string): never {
throw new Error(message);
}
// 无限循环函数
function eternalLoop(): never {
while(true) {
// 永远执行
}
}
// 使用场景:关键操作前验证
function saveUser(user: User) {
if (!user.isValid()) {
panic("Invalid user cannot be saved!");
}
// 验证通过后的保存逻辑
}
Never 类型的高级应用
1. 类型空间中的空集
在类型系统中,never 是空集,因此具有以下数学属性:
type T1 = string & never; // never (任何类型与 never 交叉 = never)
type T2 = string | never; // string (联合类型中 never 消失)
type T3 = keyof never; // never
type T4 = never[any]; // never
2. 在条件类型中的过滤
never 在条件类型的分发机制中会过滤掉不匹配的类型:
type Filter<T, U> = T extends U ? T : never;
type Numbers = Filter<string | number | boolean, number>;
// 结果:number
// 实际应用:从对象中提取函数属性
type FunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
}[keyof T];
type User = {
id: number;
name: string;
save: () => void;
};
type UserFunctionKeys = FunctionKeys<User>; // "save"
3. 类型空间中的防御性编程
使用 never 阻止不可能的分支:
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
type Direction = "up" | "down";
function move(dir: Direction) {
switch(dir) {
case "up":
return "Moving up";
case "down":
return "Moving down";
default:
return assertNever(dir); // 安全防护
}
}
4. Tuple 中的占位符
在元组类型中使用 never 来禁止特定索引位置的赋值:
type SafeTuple = [string, never, number];
const tuple: SafeTuple = ["first", /* 不能赋值 */, 3];
// ✅ 合法,第二个位置保持未初始化
// 尝试赋值会导致错误
tuple[1] = "something"; // ❌ 错误:不能将类型"string"分配给类型"never"
实际应用场景
1. Redux Action 处理
在 Redux reducer 中使用 never 进行穷尽性检查,确保处理所有 action:
type Actions =
| { type: 'ADD_TODO'; text: string }
| { type: 'DELETE_TODO'; id: number }
| { type: 'TOGGLE_TODO'; id: number };
function todoReducer(state: Todo[], action: Actions) {
switch (action.type) {
case 'ADD_TODO':
// 处理逻辑
break;
case 'DELETE_TODO':
// 处理逻辑
break;
case 'TOGGLE_TODO':
// 处理逻辑
break;
default:
const exhaustiveCheck: never = action;
return exhaustiveCheck;
}
}
// 当添加新 action 但未更新 reducer 时:
// { type: 'EDIT_TODO'; id: number; text: string } -> ❌ 类型错误
2. 高级类型工具库
在类型工具中使用 never 构建复杂类型:
// 从 T 中排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
// 获取函数参数类型
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// 获取 Promise 的解决值类型
type UnpackPromise<T> =
T extends Promise<infer U> ? U : never;
3. 防御性 API 设计
在 API 边界使用 never 强制要求编译时检查:
interface ApiResponse<T, E = never> {
data: T;
error?: E;
}
// 成功的响应:没有 error 类型
type SuccessResponse = ApiResponse<{ id: number }>;
// 错误响应:明确 error 类型
type ErrorResponse = ApiResponse<number, { code: number; message: string }>;
// 使用场景
function handleResponse(response: ApiResponse<any>) {
if (response.error) {
// 类型系统知道这里有 error 对象
console.error(response.error.message);
} else {
// 类型系统保证这里的 data 安全
console.log("Data:", response.data);
}
}
Never 类型的陷阱与误区
-
误用作为变量类型:
let mistake: never = 1; // ❌ 永远无法赋值 -
混淆
never和void:function example(): void { throw new Error(); // 正确做法应是返回 never } -
过度复杂的类型操作:
// 过于复杂的类型操作可能产生未预期的 never type Confusing = { [K: string]: never } & string;
最佳实践原则
-
使用
never进行穷尽性检查:在 switch/case 或 if/else 链的 default 分支中用never捕获未处理的情况 -
为永不返回的函数指定返回类型:明确标记
panic()、infiniteLoop()等函数返回never -
在类型操作中利用
never的过滤特性:使用never在条件类型中排除不想要的类型 -
避免直接使用
never定义变量:因为没有任何值可以赋值给never类型的变量 -
与类型断言配合使用:
function parseJSON(text: string): unknown { try { return JSON.parse(text); } catch (e) { // 错误情况下不返回任何值 return undefined as never; } }
掌握类型系统的底层基础
never 类型是 TypeScript 类型系统中的基础构建块,理解和熟练运用它可以带来以下收益:
- 🔍 更强的类型安全:通过穷尽性检查捕获未处理的情况
- 🛠️ 更精确的类型操作:在条件类型和映射类型中过滤无效状态
- ✨ 更清晰的代码意图:明确标记永不返回的函数
- 🧠 更深层的类型系统理解:掌握类型空间的数学基础
在实际项目中,合理利用 never 类型就像是拥有了一个编译时的逻辑检查器,它能在代码运行前捕获许多潜在错误。尝试在下一次编写复杂类型逻辑时思考:"这里可以使用 never 来保证类型安全吗?"这可能会是你掌握 TypeScript 高级类型系统的关键一步。
"在类型系统的宇宙中,
never是奇点——一个容纳一切又排斥一切的点。" —— TypeScript 类型哲学