Skip to content

TypeScript 中的灵活性与安全性:any vs unknown

在探索 TypeScript 强类型系统的旅程中,我们总会遇到一些特殊情况。比如,当处理来自第三方库、API 响应或用户输入的数据时,我们可能无法在开发阶段预知一个值的具体类型。

为了应对这些动态和不确定的场景,TypeScript 提供了两个特殊的类型:anyunknown。它们都允许我们绕过常规的类型检查,但其背后的设计哲学和安全级别却截然不同。

any:无所不能的“逃生舱”

any 类型是 TypeScript 中的一个“逃生舱”。当我们为一个变量标注为 any 类型时,实际上是在告诉 TypeScript 编译器:“请不要对这个变量进行任何类型检查。

这赋予了我们极大的灵活性,让这个变量的行为退回到了纯粹的 JavaScript 模式。我们可以对它进行任何操作,包括赋任何类型的值、调用任何方法、访问任何属性,无论这些操作是否合理,编译器都不会提出任何异议。

代码示例:

typescript
let flexible: any = 4;

// 我们可以随意改变它的类型
flexible = "Now I'm a string";
flexible = true;

// 我们可以调用任何方法,即使它不存在
// 编译器不会报错,但会在运行时抛出错误
flexible.thisMethodDoesNotExist(); 

// 我们可以访问任何属性
console.log(flexible.someProperty);

any 的问题:一把双刃剑

any 带来的便利是显而易见的,它让我们可以快速地在 TypeScript 项目中集成那些没有类型定义的旧有 JavaScript 代码。

然而,这种便利是有代价的。过度使用 any 会严重削弱 TypeScript 的核心优势——类型安全。它像一个“类型黑洞”,会污染与之交互的其他变量的类型推断。一个 any 类型的值,可以被赋值给任何类型的变量,这就在类型系统中打开了一个缺口,可能会在应用的深层引发难以追踪的运行时错误。

因此,我们的基本原则应该是:尽可能避免使用 any。它应该是我们最后的选择,而不是常规手段。

unknownany 的类型安全替代方案

认识到 any 的潜在风险后,TypeScript 3.0 引入了一个更优秀的解决方案:unknown 类型。

unknownany 一样,可以接收任何类型的值。但它们之间有一个根本性的区别:

unknown 类型的变量,在没有被明确地检查和断言其类型之前,你不能对它进行任何操作。

unknown 就像一个需要开箱检查的神秘包裹。你知道你收到了一个东西(unknown),但在确认它到底是什么(通过类型检查)之前,你不能使用它。

代码示例:

typescript
let mystery: unknown = "A secret message";

// 错误:对象类型为 "unknown"。
// 我们不能直接调用一个 string 方法
// mystery.toUpperCase(); 

// 错误:对象类型为 "unknown"。
// 我们不能把它赋值给一个已知类型的变量
// let message: string = mystery;

如上所示,TypeScript 阻止了我们对 unknown 类型的值进行任何不安全的操作。

如何正确使用 unknown

要使用 unknown 类型的变量,我们必须先执行类型收窄(Type Narrowing),让 TypeScript 确信在某个代码块中,这个变量是某个具体的类型。

最常见的方式是使用 typeofinstanceof 或类型断言。

代码示例:

typescript
let mystery: unknown = "Another secret message";

// 1. 使用 typeof 进行类型收窄
if (typeof mystery === 'string') {
  // 在这个 if 代码块内,TypeScript 知道 mystery 是一个 string
  console.log(mystery.toUpperCase()); // 正确!
}

let maybeANumber: unknown = 100;

// 2. 使用类型断言(Type Assertion)
let score = maybeANumber as number;
console.log(score.toFixed(2)); // 正确!

结论:何时使用 any vs unknown

  • unknown:当你确实不知道一个变量的类型,但希望在后续的操作中保持类型安全时,unknown 是你的首选。它强制你在使用变量前进行必要的类型检查,遵循了 TypeScript 的核心理念。
  • any:仅在你需要快速绕过类型检查,并且清楚地知道这样做的风险时使用。一个典型的场景是,在一个大型 JavaScript 项目的迁移初期,为了让项目先跑起来,可能会临时使用 any。但长远来看,这些 any 都应该被逐步替换为更具体的类型或 unknown

简单来说,能用 unknown 就不用 anyunknown 在提供灵活性的同时,守住了类型安全的底线,是 TypeScript 中处理未知类型值的现代且推荐的方式。

不知道说啥了很无语了