如何通过 “never” 类型防止生产环境中的代码出错
Source: Dev.to
如何使用 never 类型防止生产环境代码出错
在 TypeScript 中,never 类型经常被误解为“没有值”。实际上,它代表 永远不会出现的值——即函数永远不会正常返回,或者代码路径永远不可能被执行。正确使用 never 可以让编译器在编译阶段捕获潜在的运行时错误,从而避免在生产环境中出现意外的崩溃。
下面我们将通过几个实际案例来说明 never 的作用以及如何在项目中加以利用。
目录
- 什么是
never类型? - 使用
never实现穷尽检查(Exhaustive Checks) - 防止不可达代码(Unreachable Code)
- 在抛异常函数中使用
never - 在泛型约束中使用
never - 结论
什么是 never 类型?
-
never表示 不可能出现的值。常见的场景包括:- 永远抛出异常的函数
- 永远进入无限循环的函数
- 通过类型系统保证的“不可达”分支
-
与
void不同,void表示函数 可能不返回任何值(即返回undefined),而never表示函数 根本不会返回。
使用 never 实现穷尽检查(Exhaustive Checks)
在处理 受限集合(如枚举或联合类型)时,使用 never 可以让 TypeScript 在新增成员时提示我们更新相应的分支逻辑。
type Action =
| { type: "increment"; amount: number }
| { type: "decrement"; amount: number }
| { type: "reset" };
function reducer(state: number, action: Action): number {
switch (action.type) {
case "increment":
return state + action.amount;
case "decrement":
return state - action.amount;
case "reset":
return 0;
default:
// 这里的 `action` 的类型会被推断为 `never`
// 如果以后在 `Action` 中添加了新成员,编译器会报错
const _exhaustiveCheck: never = action;
return _exhaustiveCheck;
}
}
要点:
default分支中将action赋值给never类型的变量_exhaustiveCheck。- 当
Action联合类型新增成员时,action将不再是never,编译器会报错提醒我们处理新分支。
防止不可达代码(Unreachable Code)
有时我们会在代码中使用 assertNever 辅助函数来显式标记“不可能到达”的路径。
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
// 示例
type Shape = { kind: "circle"; radius: number } | { kind: "square"; size: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle":
return Math.PI * s.radius ** 2;
case "square":
return s.size ** 2;
default:
return assertNever(s); // 如果以后添加了新形状,这里会报错
}
}
assertNever的返回类型也是never,这意味着调用它的代码路径 永远不会继续执行,从而帮助我们捕获遗漏的分支。
在抛异常函数中使用 never
当函数的唯一目的就是抛出错误时,使用 never 可以让调用者的类型推断更加精准。
function fail(message: string): never {
throw new Error(message);
}
// 用法示例
function getUser(id: string) {
const user = database.find(u => u.id === id);
if (!user) {
return fail(`User with id ${id} not found`);
}
return user;
}
fail返回never,所以getUser的返回类型会自动推断为User(而不是User | undefined),从而消除不必要的空值检查。
在泛型约束中使用 never
never 还能帮助我们在 条件类型 中实现更严格的约束。
type IsNever<T> = [T] extends [never] ? true : false;
// 示例
type Test1 = IsNever<never>; // true
type Test2 = IsNever<string>; // false
- 通过把待检查的类型包装在元组中 (
[T]) 可以防止分配式条件类型的分配行为,从而得到准确的判断。
结论
never并不是“空”或“未定义”,而是 永远不可能出现的类型。- 利用
never可以在 编译阶段 捕获:- 未处理的联合类型成员
- 不可达代码路径
- 永不返回的函数
- 在大型代码库中引入
never的穷尽检查,可显著降低因忘记处理新分支而导致的生产环境错误。
通过在合适的地方使用 never,我们可以让 TypeScript 成为更强大的防御性工具,让代码在 写入生产之前 就已经被“自检”了一遍。祝你在项目中玩转 never,写出更安全、更可靠的代码!
什么是 never 类型?
在 TypeScript 中,never 表示 不可能发生的事。
它 不是 “返回空” 或 “空值”。它表示一条不可能到达的代码路径。
如果一个值的类型是 never,这意味着:
- 没有任何可能的运行时值
- 如果执行到这里,说明出现了错误
简单示例
function crash(): never {
throw new Error("Boom");
}
此函数永远不会正常结束。
function infiniteLoop(): never {
while (true) {}
}
执行永远不会在此之后继续,因此返回类型为 never。
让 never 强大的唯一规则
never 可以赋值给所有类型,但没有任何类型(never 本身除外)可以赋值给 never。
let x: never;
x = "hello"; // ❌ error
x = 123; // ❌ error
如果 TypeScript 发现有 真实值 被赋给 never,构建将会失败。我们将利用这条规则来阻止生产环境的 bug。
设置:常见的 React 模式
想象一个组件,根据 type 字段渲染 UI 块(CMS 块、功能标记、工作流——非常常见)。
type Block =
| { type: "hero"; title: string }
| { type: "cta"; text: string };
渲染器
function RenderBlock({ block }: { block: Block }) {
switch (block.type) {
case "hero":
return <h2>{block.title}</h2>;
case "cta":
return <div>{block.text}</div>;
}
}
一切看起来都正常:
- TypeScript 很开心
- CI 通过
- 没有运行时错误
生产环境 Bug(静默)
type Block =
| { type: "hero"; title: string }
| { type: "cta"; text: string }
| { type: "banner"; image: string };
他们忘记更新 RenderBlock。会发生什么?
- 没有 TypeScript 错误
- 没有构建失败
- 组件返回
undefined→ 什么也不渲染
生产 UI 静默破坏。 这是最糟糕的 bug 类型。
为什么 TypeScript 没有拯救你
从 TypeScript 的角度来看,函数可能返回 JSX.Element | undefined,这在技术上是有效的。TypeScript 并不假设 switch 已经穷尽所有情况,因此错误仍然会通过。
错误的“修复”
像这样添加一个 default 分支:
default:
return null;
仍然留下问题:
- 没有编译器错误
- UI 仍然损坏
- 问题变得更难被注意到
我们需要 TypeScript 大声报错。
介绍 never
更改组件中的 一行 代码:
function RenderBlock({ block }: { block: Block }) {
switch (block.type) {
case "hero":
return <h2>{block.title}</h2>;
case "cta":
return <div>{block.text}</div>;
default:
const _exhaustiveCheck: never = block;
return null;
}
}
结果

关键行是:
const _exhaustiveCheck: never = block;
有哪些更改?
此行告诉 TypeScript:“如果 block 到达这里,代码就是错误的。”
当 banner 存在时,TypeScript 看到 block 可以是 { type: "banner" },这 not assignable to never,从而产生编译时错误。CI 失败,错误永远不会被发布。
真正的收益
它不仅仅是关于穷尽性检查;它提供了以下保证:
如果代码能够编译,则所有情况都已处理。
这就是生产安全。
为什么在真实代码库中这很重要
该模式在以下情况下表现突出:
- CMS 架构演进
- 多个团队共同使用相同的联合类型
- UI 渲染依赖于配置或 API 响应
- Reducer 或工作流随时间增长
只要 类型变化快于实现,never 就能保护你。
心智模型
never 的含义是:“此代码路径不应存在。”
如果 TypeScript 证明它 可以 存在,则构建会失败——这正是设计如此。
结论
- TypeScript 默认不保证穷尽处理。
- React 组件在生产环境中可能悄然出错。
never将缺失的情况转化为编译错误。
一行代码即可防止整类 bug:
const _exhaustiveCheck: never = value;
在任何沉默可能危险的地方使用它。