TS类型系统双雄:any和unknown的爱恨情仇

109 阅读3分钟

前言

"为什么我用any被同事吐槽?"、"unknown到底比any强在哪?" 今天咱们就唠唠TS里这对让人又爱又恨的兄弟类型。别看它们长得像,用起来可是差之千里!


一、先找共同点:这对兄弟哪里像?

1.1 都是类型系统的"逃生通道"

当遇到不确定类型的场景时,它们都能救急:

// 比如处理JSON数据这种典型场景
const data1: any = JSON.parse('{"name": "老王"}');
const data2: unknown = JSON.parse('{"name": "老王"}');

1.2 都能兼容所有类型

let anything: any = "字符串";
anything = 123;       // 没问题
anything = new Date();// 也没问题

let anything2: unknown = "字符串";
anything2 = 123;      // 同样OK

二、再看差异点:它们哪里不对付?

2.1 自由度不同(关键区别!)

  • any是脱缰的野马
    用了any就等于对TS说:"你甭管我了,出问题算我的!"

    let danger: any = "hello";
    danger.toFixed(2); // 编译不报错,运行就爆炸!
    
  • unknown是带着GPS的越野车
    必须明确告诉TS路线才能开:

    let safe: unknown = "hello";
    // safe.toUpperCase(); // 直接报错!必须证明它是字符串
    
    if (typeof safe === "string") {
      safe.toUpperCase(); // 现在安全了
    }
    

2.2 类型传播差异

  • any会污染全家桶

    const arr: any[] = ["test", 123];
    arr.forEach(item => item.toUpperCase()); // 这里会坑到数字元素
    
  • unknown让人保持警惕

    const arr: unknown[] = ["test", 123];
    arr.forEach(item => {
      // item.toUpperCase() // 必须做类型检查!
    });
    

三、使用场景指南:什么时候该翻谁的牌子?

3.1 该用any的三大场景

  1. 抢救祖传JS代码

    // 旧代码迁移时临时使用
    function legacyFunc(input: any) {
      // 先保证能跑起来再说
    }
    
  2. 对接玄学第三方库

    // 比如某些没有类型声明的jQuery插件
    declare const magic: any;
    magic.doSomethingWeird();
    
  3. 快速原型开发

    // 写demo时先any一把梭
    const demoData: any = {
      /* 随便塞数据 */
    };
    

3.2 该用unknown的三大场景

  1. 处理薛定谔的数据

    // 比如用户输入/接口返回等不确定数据
    function parseUserInput(input: unknown) {
      // 必须做类型检查!
    }
    
  2. 写通用工具函数

    // 安全的数据验证函数
    function safeStringify(data: unknown): string {
      // 这里必须处理各种类型
    }
    
  3. 类型安全的装13写法

// 传统危险写法
type RiskyResponse<T> = {
  data: T; // 直接暴露原始类型
}

const res: RiskyResponse<any> = await fetchAPI();
res.data.toUpperCase(); // 可能运行时崩溃!

// 安全升级版
type SafeWrapper<T> = {
  data: T extends infer U ? unknown : never; // 无论T是什么,data始终是unknown
}

const safeRes: SafeWrapper<APIResponse> = await fetchAPI();
// safeRes.data.toUpperCase(); // 这里会直接报错!

// 必须通过类型守卫才能使用
if (typeof safeRes.data === 'string') {
  safeRes.data.toUpperCase(); // 安全操作
}

设计原理

  1. T extends infer U:通过条件类型捕获原始类型信息(保留类型推导能力)
  2. unknown:强制使用者进行类型检查(相当于给数据上了把锁)
  3. 最终效果:像快递包裹一样,必须拆箱检查才能使用

类比理解

  • 传统方式:收到快递直接拆包使用(可能被划伤)
  • SafeWrapper:收到的是带刀片的包裹,必须用专用工具拆开(类型检查)才能安全取出物品

实际应用价值

  • 在SDK开发中强制消费方验证数据
  • 防止接口字段变更导致连锁错误
  • 配合Zod等校验库实现端到端类型安全

四、防坑小贴士

4.1 从any迁移到unknown的姿势

  1. 渐进式改造
    迁移路线图:any → unknown → 具体类型

  2. 配置ESLint当保镖

    {
      "rules": {
        "@typescript-eslint/no-explicit-any": "warn",
        "@typescript-eslint/no-unsafe-argument": "error"
      }
    }
    

4.2 强行用any也不丢人?!

特殊场景允许使用(但要做好标记):

// 请用特殊注释说明理由
// @ts-ignore-next-line 因XX原因临时使用any
const temporarySolution: any = getWeirdData();

五、一句话总结

any是方便面(偶尔应急),unknown是家常菜(日常必备)

下次遇到类型不确定时,不妨多问自己:

"这个场景真的需要any吗?用unknown是不是更安全?"

记住:用unknown一时麻烦,用any天天提心吊胆!

(完)