☰
JavaScript / TypeScript 易踩坑知识点 —— TypeScript 专属类
2026/10/9 5:19:57 网站建设 项目流程

JavaScript / TypeScript 易踩坑知识点 —— TypeScript 专属类

一、滥用 any

问题说明

any会完全关闭 TypeScript 的类型检查,等于放弃了 TS 的核心价值。

踩坑示例

// ❌ 滥用 anyconstdata:any=fetchData()data.someMethod()// 编译不报错,运行时可能报错// ❌ 用 any 当万能类型functionhandleInput(val:any){val.length// 如果传进来不是数组或字符串,运行时报错}// ✅ 用 unknown 替代 any(安全类型)constdata:unknown=fetchData()// data.someMethod() // ❌ 编译报错,必须先类型收窄if(typeofdata==='object'&&data!==null&&'someMethod'indata){data.someMethod()// ✅ 类型收窄后可以调用}// ✅ 用类型断言(确定类型时用)constdata=fetchData()asApiResponse data.items// ✅ 类型安全

any 与 unknown 的区别

特性anyunknown
可以调用任意方法✅❌(需先收窄)
可以赋给任意类型✅✅
任意类型可赋给它✅❌(需先收窄)
安全性不安全安全

二、函数重载顺序

问题说明

TypeScript 函数重载时,更具体的签名要放在更通用的前面,否则会被"吞掉"。

踩坑示例

// ❌ 顺序错误:通用签名在前,具体签名永远到不了functionhandleValue(val:string|number):voidfunctionhandleValue(val:string):void{console.log('string:',val.toUpperCase())}functionhandleValue(val:number):void{console.log('number:',val.toFixed(2))}// 调用时handleValue(42)// ❌ 编译报错:没有匹配的重载(string | number 匹配了第一个)// ✅ 顺序正确:具体签名在前functionhandleValue(val:string):voidfunctionhandleValue(val:number):voidfunctionhandleValue(val:string|number):void{if(typeofval==='string'){console.log('string:',val.toUpperCase())}else{console.log('number:',val.toFixed(2))}}handleValue(42)// ✅ 正确

三、strictNullChecks 未开启

问题说明

不开启strictNullChecks时,null和undefined可以赋给任何类型,很多 bug 会在运行时才暴露。

踩坑示例

// tsconfig.json 中必须开启{"compilerOptions":{"strictNullChecks":true}}// 开启后:letname:stringname=null// ❌ 编译报错name=undefined// ❌ 编译报错name='hello'// ✅// 允许 null/undefined 时,用联合类型letname:string|null=nullname='hello'// ✅name=null// ✅// 非空断言操作符 !(告诉编译器"一定不为空")letname:string|null=nullname='hello'console.log(name!.length)// ✅ 告诉编译器 name 不为 null// ⚠️ 但如果 name 真的是 null,运行时会报错

非空断言操作符 ! 的坑

// ❌ 滥用非空断言,运行时可能崩溃constelement=document.getElementById('my-id')!element.textContent='hello'// 如果元素不存在,运行时 TypeError// ✅ 先做类型守卫constelement=document.getElementById('my-id')if(element){element.textContent='hello'// ✅ 类型安全}// ✅ 可选链 + 空值合并document.getElementById('my-id')?.textContent='hello'

四、类型断言 as 的坑

问题说明

as只是告诉编译器"相信我,这个类型是对的",但不会做运行时检查。用错了会导致运行时错误。

踩坑示例

// ❌ 断言错误类型,运行时崩溃constdata=JSON.parse('{"name": "Alice"}')asstring[]data[0].toUpperCase()// ❌ TypeError: data[0].toUpperCase is not a function// ✅ 正确断言constdata=JSON.parse('{"name": "Alice"}')as{name:string}data.name.toUpperCase()// ✅// ✅ 更好的方式:定义接口 + 类型守卫interfaceUser{name:stringage:number}functionisUser(val:unknown):valisUser{returntypeofval==='object'&&val!==null&&'name'inval}constdata=JSON.parse('{"name": "Alice", "age": 25}')if(isUser(data)){console.log(data.name)// ✅ 类型安全}

五、泛型约束

问题说明

泛型不加约束时,只能使用object上共有的方法,无法访问特定属性。

踩坑示例

// ❌ 没有约束,无法访问 length 属性functiongetLength<T>(val:T):number{returnval.length// ❌ 编译报错:T 上不存在 length}// ✅ 添加约束functiongetLength<Textends{length:number}>(val:T):number{returnval.length// ✅}// ✅ 更精确的约束functiongetProperty<T,KextendskeyofT>(obj:T,key:K):T[K]{returnobj[key]}constuser={name:'Alice',age:25}getProperty(user,'name')// ✅getProperty(user,'address')// ❌ 编译报错

六、Record 与 Record<string, any>

问题说明

Record<K, V>是 TS 内置的工具类型,但Record<string, any>等同于{},失去了类型检查的意义。

踩坑示例

// ❌ 等同于 {},没有类型检查constconfig:Record<string,any>={port:3000,host:'localhost'}config.port.toFixed(2)// ✅ 但 port 可能是 string,运行时可能报错// ✅ 定义明确的类型interfaceServerConfig{port:numberhost:stringdebug?:boolean}constconfig:ServerConfig={port:3000,host:'localhost'}config.port.toFixed(2)// ✅ 类型安全config.timeout// ❌ 编译报错:timeout 不存在

七、interface 与 type 的选择

问题说明

interface和type在很多场景下可以互换,但有一些关键区别。

对比表

特性interfacetype
对象类型定义✅✅
类型别名(联合/交叉)❌✅
声明合并✅❌
类实现✅✅(type 用 intersection)
计算属性名❌✅

踩坑示例

// interface 可以声明合并interfaceUser{name:string}interfaceUser{age:number}// User = { name: string, age: number }// type 不能合并,但可以做联合类型typeStatus='active'|'inactive'|'pending'// type 可以做交叉类型typeAdminUser=User&{role:'admin'}// 类实现 interfaceinterfaceIAnimal{name:stringspeak():void}classDogimplementsIAnimal{name='Dog'speak(){console.log('Woof')}}

八、never 类型

问题说明

never表示"永远不会发生的值",用于 exhaustive check 和确定函数不会返回。

踩坑示例

// 穷举检查(exhaustive check)typeShape='circle'|'square'|'triangle'functiongetArea(shape:Shape):number{switch(shape){case'circle':returnMath.PI*1// 假设半径为1case'square':return1case'triangle':return0.5// ✅ 如果新增了 'pentagon',这里会报错default:{const_exhaustiveCheck:never=shapereturn_exhaustiveCheck// ❌ 如果 shape 类型扩展了,这里会报类型错误}}}// 标记函数永远不会返回functionthrowError(msg:string):never{thrownewError(msg)}functionassert(condition:boolean,msg:string):assertscondition{if(!condition){thrownewError(msg)}}

总结对照表

坑点错误写法正确写法
滥用 anyconst x: any = ...const x: unknown = ...+ 类型收窄
函数重载顺序通用签名在前具体签名在前
strictNullChecks未开启tsconfig 中开启
非空断言滥用obj!.prop先做类型守卫
类型断言错误data as WrongType类型守卫 + 类型断言
Record<string, any>失去类型检查定义明确的 interface
interface vs type混用对象类型用 interface,联合/交叉用 type
never 穷举不处理 defaultconst _exhaustiveCheck: never = val

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询