欢迎光临
我们一直在努力

TypeScript中any的罪与罚:为什么要避免使用any?

TypeScript中any的罪与罚:为什么要避免使用any?

  • any的"罪":类型系统的漏洞
    • any到底是什么?
    • any的类型传染
  • any的"罚":类型灾难
    • any 会打破类型契约
    • any会掩盖重构代码时的错误
    • any会屏蔽类型设计问题
    • any会丧失语言服务
  • 严格模式:给any戴上镣铐
    • noImplicitAny:禁止隐式any
    • strict:最严格的检查
    • @ts-expect-error:明确忽略错误
  • unknown:安全的any替代方案
    • unknown是什么?
  • unknown vs any:行为对比
  • 何时可以使用any?
  • 使用any注意事项
    • 限制any的作用范围
    • 添加明确的注释
    • 逐步替换
  • 结语

在之前的文章中 TypeScript基础类型全解 ,我们了解到了 TypeScript 的所有基础类型,里面提到了 any 类型。那么 any 类型到底是什么呢?其实,any 就如潘多拉魔盒:用起来一时爽,维护时火葬场。本篇文章将通过各种案例,来演示 any 是如何悄无声息地破坏代码的类型安全,以及如何使用 unknown 优雅地解决问题。

any的"罪":类型系统的漏洞

any到底是什么?

any 是 TypeScript 中的一种特殊类型,它会告诉编译器:“别管这个值是什么类型,我全权负责”。即:any 会放弃所有类型检查。

let anything: any = "我是一个字符串";

anything = 42; // ✅ 可以变成数字
anything = true; // ✅ 可以变成布尔值
anything = { key: "value" }; // ✅ 可以变成对象
anything(); // ✅ 可以当作函数调用(尽管会崩溃)
anything.nonExistentProperty.anotherProperty; // ✅ 可以访问不存在的属性

any的类型传染

我们先来看一段简单的代码:

let age:number;
// age = '12'; // ❌ 不能将类型“string”分配给类型“number”
age = '12' as any; // ✅
age += 1;
console.log(age);

上述代码输出结果是多少呢?13吗?不对,真实结果是:'121' 。因为在类型声明中说明了 age 是 number 类型,但使用了 any 断言分配给了一个 string 值,因此类型检查器仍然会相信它是一个 number,但代码运行时会以 string 处理。这就是 any 的类型传染问题,也是 any 没有类型安全的原因。

关于类型断言的话题,在后面的文章中会详细讲解。

any的"罚":类型灾难

any 会打破类型契约

当我们在编写一个函数时,其实我们是在指定一个契约:调用者需要传递特定类型的参数,函数会返回一个特定类型的输出。但 any 会打破这些类型契约:

function sum(a: number, b: number) {
return a + b;
}
let a: any = '1';
let b: any = '2';
sum(a, b);

上述代码中,sum() 函数期望接收两个 number 类型的参数,而不是 string。any 类型打破了这种契约,这会很麻烦,因为 JavaScript 会在类型之间进行隐式转换,导致代码可以正常运行,但得不到正确的结果。

any会掩盖重构代码时的错误

当我们要对代码进行重构或者修改时,即使存在漏改,但编译器也不会报错:

// 初始版本:使用具体类型
interface User {
id: number;
name: string;
age: number;
}

function greetUser(user: User): string {
return `Hello, ${user.name}! You are ${user.age} years old.`;
}

现在,我们需要把 age 属性更改为 userAge 属性,在 greetUser() 函数传参时,使用 any 会有哪些问题呢?

interface User {
id: number;
name: string;
userAge: number;
}
function greetUser(user: any): string {
return `Hello, ${user.name}! You are ${user.age} years old.`;
}

其结果是:编译通过,但运行时报错!

any会屏蔽类型设计问题

对于一个复杂的类型定义可能会很长,相比写几十个属性类型而言,一个 any 确实能省很多事。但随之而来的,也会有很多问题,比如代码审查时,需要重新梳理和构建类型信息。我们无法从简单的代码层面来判断这个设计是好是坏,是否合理。

any会丧失语言服务

当一个符号有一个类型时,TypeScript的语言服务(Language Service)能够提供自动补全和上下文文档信息。但对于带有 any 类型的符号,会丧失这种功能,只能人为自己写了!

严格模式:给any戴上镣铐

针对 any 可能带来的种种问题,TypeScript 提供了严格模式选项,以此来限制 any 的使用:

noImplicitAny:禁止隐式any

// 在tsconfig.json中启用
{
"compilerOptions": {
"noImplicitAny": true
}
}

// ❌ 错误:参数'value'隐式具有'any'类型
function process(value) {
return value.toUpperCase();
}

// ✅ 正确:明确指定类型
function process(value: string) {
return value.toUpperCase();
}

strict:最严格的检查

{
"compilerOptions": {
"strict": true // 包含所有严格检查
}
}

// strict包含:
// – noImplicitAny
// – strictNullChecks
// – strictFunctionTypes
// – strictBindCallApply
// – strictPropertyInitialization
// – noImplicitThis
// – alwaysStrict

@ts-expect-error:明确忽略错误

// 有时确实需要any,但应该明确标注

// ❌ 不好:直接使用any
function dangerousFunction(data: any) {
// …
}

// ✅ 更好:使用unknown,然后明确断言
function saferFunction(data: unknown) {
if (typeof data === 'object' && data !== null) {
const obj = data as { id: number };
// 明确知道自己在做什么
}
}

// ✅ 最好:使用@ts-expect-error注释
function trickyFunction() {
// @ts-expect-error – 这里确实需要绕过类型检查
const result: any = legacyLibrary.call();
return result;
}

unknown:安全的any替代方案

unknown是什么?

unknown 是 TypeScript 3.0 引入的类型,它表示:“我不知道这是什么类型,所以你在用之前必须先检查”。

let uncertain: unknown = "Hello World";

// ❌ 不能直接使用
// uncertain.toUpperCase(); // 错误:'unknown'类型上不存在'toUpperCase'
// uncertain(); // 错误:不能调用
// uncertain.property; // 错误:不能访问属性

// ✅ 必须先进行类型检查
if (typeof uncertain === "string") {
console.log(uncertain.toUpperCase()); // 现在可以了
}

unknown vs any:行为对比

特性anyunknown
赋值给其他类型 ✅ 总是可以 ❌ 需要类型断言
调用方法 ✅ 总是可以 ❌ 需要类型检查
访问属性 ✅ 总是可以 ❌ 需要类型检查
作为函数调用 ✅ 总是可以 ❌ 需要类型检查
类型安全 ❌ 不安全 ✅ 安全
用途 放弃类型检查 暂时未知类型

它们的关键区别在于:

  • any:我保证没问题(编译器相信你)
  • unknown:我不确定,你要先检查(编译器要求你证明)

何时可以使用any?

虽然我们要避免any,但在某些特定情况下,它还是有用的,比如:

  • 迁移JavaScript项目,在迁移期间,可以暂时使用any。
  • 测试代码,测试中可能需要模拟各种情况。
  • 与没有类型的第三方库交互。

使用any注意事项

我们在使用 any 时,要注意些什么呢?

限制any的作用范围

function processData(data: any) {
// 立即转换为安全类型
const safeData = data as { id: number; name: string };
// 后续使用safeData,而不是data
return safeData;
}

添加明确的注释

function riskyOperation(input: any /* 来自旧系统,无法修改 */) {
// 业务逻辑
}

逐步替换

  • 第一步:标记为any
  • 第二步:添加类型断言
  • 第三步:完全移除any
  • 第四步:添加验证
  • // 第一步:标记为any
    let value: any = getFromLegacySystem();

    // 第二步:添加类型断言
    let value: any = getFromLegacySystem();
    const typedValue = value as string;

    // 第三步:完全移除any
    let value: string = getFromLegacySystem() as string;

    // 第四步:添加验证
    function safeGetString(): string {
    const value = getFromLegacySystem();
    if (typeof value !== "string") {
    throw new Error("Expected string");
    }
    return value;
    }

    结语

    本文主要介绍了 any 和 unknown 两种类型的对比,以及 any 的使用危害等。在实际项目中还是推荐开启strict模式,并将所有 any 替换为 unknown,添加必要的类型检查。

    对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!

    赞(0)
    未经允许不得转载:171主机测评 » TypeScript中any的罪与罚:为什么要避免使用any?
    分享到: 更多 (0)

    评论 抢沙发

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