前言
"为什么我用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的三大场景
-
抢救祖传JS代码
// 旧代码迁移时临时使用 function legacyFunc(input: any) { // 先保证能跑起来再说 } -
对接玄学第三方库
// 比如某些没有类型声明的jQuery插件 declare const magic: any; magic.doSomethingWeird(); -
快速原型开发
// 写demo时先any一把梭 const demoData: any = { /* 随便塞数据 */ };
3.2 该用unknown的三大场景
-
处理薛定谔的数据
// 比如用户输入/接口返回等不确定数据 function parseUserInput(input: unknown) { // 必须做类型检查! } -
写通用工具函数
// 安全的数据验证函数 function safeStringify(data: unknown): string { // 这里必须处理各种类型 } -
类型安全的装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(); // 安全操作
}
设计原理:
T extends infer U:通过条件类型捕获原始类型信息(保留类型推导能力)unknown:强制使用者进行类型检查(相当于给数据上了把锁)- 最终效果:像快递包裹一样,必须拆箱检查才能使用
类比理解:
- 传统方式:收到快递直接拆包使用(可能被划伤)
- SafeWrapper:收到的是带刀片的包裹,必须用专用工具拆开(类型检查)才能安全取出物品
实际应用价值:
- 在SDK开发中强制消费方验证数据
- 防止接口字段变更导致连锁错误
- 配合Zod等校验库实现端到端类型安全
四、防坑小贴士
4.1 从any迁移到unknown的姿势
-
渐进式改造
迁移路线图:any → unknown → 具体类型 -
配置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天天提心吊胆!
(完)