TypeScript any 与 unknown

在实际开发中,我们偶尔会遇到一些 “未知” 的情况:比如接收一个来自第三方 API 的复杂数据,或者重构一段古老的 JavaScript 代码。这个时候,我们往往无法立刻确定数据的具体类型。

为了应对这些特殊情况,TypeScript 提供了两种特殊的类型:any 和 unknown。

TypeScript 的 “后门” —— any

在 TypeScript 中,如果我们将一个变量的类型定义为 any(任意类型),就相当于直接告诉编译器:“不用管我在干嘛。” 此时,TypeScript 就会完全放弃对这个变量的所有类型检查。

语法:

const 变量名: any = 值;

示例 1:使用 any 类型

// 将 data 定义为 any 类型
const data: any = "绿叶网";

// 下面各种离谱的操作,TypeScript 都不会报错
console.log(data.name); 
data.foo();

分析:

使用 any 类型之后,会导致 TypeScript 类型检查完全失效。

在这个例子中,data 明明是一个字符串,但我们试图去访问它的 name 属性,甚至把它当成一个函数 foo() 去调用。如果在正常的 TypeScript 代码中,编辑器早就飘红警告了。但由于我们这里使用了 any,此时 TypeScript 选择了视而不见。

实际上,这种情况是非常危险的。因为这些代码在编写阶段不会有任何报错,但一旦真正在浏览器里跑起来,程序立马就会崩溃。

因此在实际开发中,我们千万不要乱用 any,否则的话 TypeScript 就真的变成了大家口中的 “AnyScript” 了。

TypeScript 的 “守门员” —— unknown

为了弥补 any 过于放纵的缺陷,TypeScript 引入了一个非常重要的新类型:unknown(未知类型)。

unknown 和 any 这两个非常像,它们都可以接收任何类型的值。但 unknown 是一位极其严谨的 “守门员”:我们可以把任何东西塞给 unknown,但在我们向 TypeScript 证明它的具体类型之前,是不能对它进行任何操作的。

示例 2:严格的 unknown

// 将 data 定义为 unknown 类型
const data: unknown = "绿叶网";

// 下面两行代码都会飘红报错
console.log(data.length); 
data.foo();

分析:

在这个例子中,虽然 data 确实是一个字符串,但由于它的类型被标记为了 unknown,然后 TypeScript 为了安全起见,直接拒绝了我们获取 length 属性的请求。编辑器会抛出错误提示:

对象的类型为 "unknown"

那么问题来了:“既然 unknown 啥都不让干,那我们定义它还有什么意义呢?” 别急嘛,这就需要用到 TypeScript 中的 “类型收窄” 了。

如何正确使用 unknown)?

在 TypeScript 中,对于 unknown 类型的变量,在使用它之前,我们必须通过条件判断(比如 typeof 或 instanceof)来缩小它的类型范围,让 TypeScript 确信它是安全的。这个过程叫做 “类型收窄(Type Narrowing)”。

提示: “类型收窄” 这个术语极其重要,小伙伴们要特别留个心眼。其中,类型收窄广泛用于处理空值、可选值、联合类型等。

示例 3:使用类型收窄

const data: unknown = "绿叶网";

// 错误调用:直接使用会报错
// console.log(data.length);

// 正确调用:通过 typeof 进行类型收窄
if (typeof data === "string") {
    console.log(data.length);
}

运行结果如下。

3

分析:

在这个例子中,我们通过 if (typeof data === "string") 进行了明确的类型判断。在 if 分支内部,TypeScript 非常聪明地推断出 data 肯定是一个字符串,于是它不仅允许我们访问 length 属性,还会自动给我们提供字符串相关的代码智能提示。

any 与 unknown 的 “污染” 与 “隔离”

前面我们讲了对这两种类型本身进行操作时的区别。除此之外,它们在赋值给其他明确类型的变量时,表现也是截然不同的。这也是 any 最危险的地方!

示例 4:any 的病毒式污染

let valAny: any = "绿叶网";

// 极其危险
let myAge: number = valAny; 

console.log(myAge);

运行结果如下。

绿叶网

分析:

在这个例子中,我们把 any 赋值给明确规定为 number 类型的变量。这是一个非常危险的操作。

myAge 明明是一个严谨的 number 类型,但因为接收了 any 类型的变量,它的防线瞬间崩溃了,TypeScript 编译器没有任何报错。

在大型项目中,一个 any 可以顺着函数的参数和返回值,悄无声息地污染整个项目,把所有严谨的类型全部变成摆设。

示例 5:unknown 的安全隔离

let valUnknown: unknown = "绿叶网";

let myAge: number = valUnknown; 
// 报错:不能将类型 “unknown” 分配给类型 “number”

分析:

这里我们试图把 unknown 赋值给明确规定为 number 类型的变量,TypeScript 编译器会直接拦截并报错。在没有通过条件判断(类型收窄)证明 valUnknown 确实是一个数字之前,TypeScript 是绝对不允许被赋值给其他有明确类型的变量。

any 与 unknown 最佳实践

在真实的企业级项目中,最容易出现 “未知类型” 的场景之一就是:解析 JSON 字符串。

我们都知道,JSON.parse() 的返回值默认就是 any 类型。这在庞大的项目中很容易 “埋雷”。作为一名优秀的前端工程师,我们应该先将其视为 unknown,然后再进行安全校验。

示例 6:安全的 JSON 解析

const jsonString = '{"name": "Jack", "age": 20}';

// 主动标记为 unknown
const userData: unknown = JSON.parse(jsonString);

// 类型收窄
if (typeof userData === "object" && userData !== null) {
    console.log("解析成功,这是一个对象!");
} else {
    console.log("数据格式错误");
}

运行结果如下。

解析成功,这是一个对象!

分析:

在更高级的企业级开发中,当我们确信 userData 是一个非空对象后,通常还会配合 TypeScript 的 “类型断言(as)” 或者 “类型谓词(is)”,甚至引入第三方的运行时校验库(如 Zod),来将它精确地转换为我们想要的完整接口类型。这部分内容,我们会在后续的进阶章节中会深入探讨。

最后来总结一下,关于 any 和 unknown 的企业级最佳实践,小伙伴们需要牢记以下两点:

  • 只要你觉得 “这个数据可能什么类型都有”,应该优先使用 unknown。虽然 unknown 用起来稍微麻烦一点(需要强制你写类型判断),但它能最大程度地保证代码在运行时的绝对安全。
  • 永远把 any 当作最后的无奈之举。除非你真的无路可走,或者在快速测试代码,否则坚决不要用 any。

上一篇: TypeScript 枚举

下一篇: TypeScript void 与 never

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号