在 TypeScript 的类型系统中,any 往往是最容易被滥用的类型。它像一扇敞开的大门,允许任何值进入,也允许任何操作发生。这种“自由”带来的代价是类型安全的彻底丧失。幸运的是,TypeScript 提供了两个更精确、更安全的替代者:unknown 和 never。理解它们,是从“能用”走向“可靠”的关键一步。
为什么 any 是危险的
any 的本质是放弃类型检查。当一个变量被标注为 any 时,TypeScript 编译器会关闭对该变量的所有静态检查。这意味着你可以对它做任何操作——调用方法、访问属性、赋值给任何其他类型——编译器都不会报错。
let value: any = "hello";
value.toFixed(2); // 编译通过,但运行时会抛出 TypeError
value = 42;
value = { name: "Alice" };
这种灵活性在迁移旧代码或处理动态内容时看似方便,但它把类型错误从编译时推迟到了运行时。在大型项目中,一个 any 可能像病毒一样传播,悄无声息地侵蚀整个类型系统的可靠性。
unknown:安全的“任意值”
unknown 是 TypeScript 3.0 引入的类型,它表示“我们暂时不知道这个值是什么类型”。与 any 的关键区别在于:unknown 是类型安全的顶部类型。
任何值都可以赋给 unknown,但 unknown 不能直接赋给其他类型,也不能直接进行任何操作。你必须先通过类型守卫(type guard)缩小它的类型范围。
let value: unknown = "hello";
// 直接操作会报错
// value.toUpperCase(); // 错误:Object is of type 'unknown'
// 必须先检查类型
if (typeof value === "string") {
console.log(value.toUpperCase()); // 现在安全了
}
使用场景
unknown 最适合用于以下场景:
- 函数参数:当你编写一个接受任意输入的函数,但需要在函数内部进行验证时。
- API 响应:从外部接口获取的数据在解析前都是未知的。
- 错误处理:
catch块中的错误类型在 TypeScript 4.4 之前默认是any,现在推荐使用unknown。
function processInput(input: unknown): string {
if (typeof input === "string") {
return input.trim();
}
if (typeof input === "number") {
return input.toString();
}
throw new Error("不支持的类型");
}
try {
// 一些可能抛出异常的操作
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error("未知错误", error);
}
}
unknown 强制你在使用值之前进行验证,这正是类型安全的核心:先证明,再使用。
never:不可能存在的类型
never 是 TypeScript 的底部类型,表示永远不会发生的值。它没有任何值,是空集。你无法将任何值赋给 never(除了 never 本身),但 never 可以赋给任何其他类型。
let impossible: never;
// 错误:不能将类型 "string" 分配给类型 "never"
// impossible = "hello";
// never 可以赋给任何类型
let str: string = impossible; // 合法
何时会出现 never
never 通常出现在以下情况:
- 抛出异常的函数:函数永远不会正常返回。
- 无限循环的函数:函数永远不会结束。
- 类型收窄后的穷尽检查:当所有可能的分支都被处理完后,剩余的类型就是
never。
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
// ...
}
}
穷尽检查的威力
never 最实用的场景之一是穷尽检查(exhaustiveness checking)。当你处理一个联合类型时,可以利用 never 确保所有情况都被覆盖。
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
| { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
case "triangle":
return (shape.base * shape.height) / 2;
default:
// 如果所有情况都处理了,shape 在这里是 never
const _exhaustive: never = shape;
throw new Error(`未处理的形状: ${_exhaustive}`);
}
}
如果将来有人给 Shape 添加了新的成员(比如 rectangle),但忘记在 area 函数中处理,编译器会在 default 分支报错:Type 'Rectangle' is not assignable to type 'never'。这种机制让重构变得安全可靠。
三者的对比与选择
| 特性 | any |
unknown |
never |
|---|---|---|---|
| 赋值给其他类型 | 可以 | 不可以 | 可以(给任何类型) |
| 接受其他类型赋值 | 可以 | 可以 | 不可以 |
| 可执行操作 | 任意 | 需类型收窄 | 无 |
| 类型安全 | 否 | 是 | 是 |
| 典型用途 | 迁移旧代码 | 外部输入、错误处理 | 穷尽检查、异常函数 |
选择原则
- 优先使用
unknown而非any:当你确实不知道类型,但希望保持安全时。 - 用
never表达“不可能”:在穷尽检查、抛出异常或无限循环的场景中。 - 尽量避免
any:如果必须使用,将其限制在最小范围内,并添加注释说明原因。
总结
TypeScript 的类型系统不仅仅是为了标注类型,更是为了在编译时捕捉错误。any 虽然方便,但它绕过了这套系统,让类型安全形同虚设。unknown 和 never 则提供了更精确的表达能力:
unknown是“我不知道,但我会先检查”——它强制你进行类型验证。never是“这不可能发生”——它帮助你在编译时发现遗漏的分支。
从今天开始,试着在代码中用 unknown 替换 any,用 never 完善你的穷尽检查。你会发现,类型系统不再是负担,而是帮你写出更健壮代码的伙伴。
未经允许不得转载:任鹏个人博客 » 理解 TypeScript 的 unknown 与 never:比 any 更安全的类型选择


朋友圈点赞图在线生成源码