在实际开发中,我们偶尔会遇到一些 “未知” 的情况:比如接收一个来自第三方 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。
