理解 TypeScript 的 unknown 与 never:比 any 更安全的类型选择

在 TypeScript 的类型系统中,any 往往是最容易被滥用的类型。它像一扇敞开的大门,允许任何值进入,也允许任何操作发生。这种“自由”带来的代价是类型安全的彻底丧失。幸运的是,TypeScript 提供了两个更精确、更安全的替代者:unknownnever。理解它们,是从“能用”走向“可靠”的关键一步。

为什么 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 通常出现在以下情况:

  1. 抛出异常的函数:函数永远不会正常返回。
  2. 无限循环的函数:函数永远不会结束。
  3. 类型收窄后的穷尽检查:当所有可能的分支都被处理完后,剩余的类型就是 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 虽然方便,但它绕过了这套系统,让类型安全形同虚设。unknownnever 则提供了更精确的表达能力:

  • unknown 是“我不知道,但我会先检查”——它强制你进行类型验证。
  • never 是“这不可能发生”——它帮助你在编译时发现遗漏的分支。

从今天开始,试着在代码中用 unknown 替换 any,用 never 完善你的穷尽检查。你会发现,类型系统不再是负担,而是帮你写出更健壮代码的伙伴。

未经允许不得转载:任鹏个人博客 » 理解 TypeScript 的 unknown 与 never:比 any 更安全的类型选择

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏