0


TypeScript 报错汇总

TypeScript 报错汇总

在这篇文章中将记录我遇到的ts错误,应该会持续更新。

有时候从错误点入手学习似乎是一个不错的选择,所以也欢迎你私信我一些ts的问题。

一、内置工具

1.1 Pick & Partial

先看看

Pick

Partial

工具的源码:

typePartial<T>={[PinkeyofT]?:T[P];};typePick<T,KextendskeyofT>={[PinK]:T[P];};

从代码和注释来看,

  • 通过Pick工具根据联合类型数据,筛选泛型T中的属性
  • 通过Partial工具将接口属性都变为可选属性

比如:

interfaceUser{
  id:number;
  age:number;
  name:string;};// 相当于: type PartialUser = { id?: number; age?: number; name?: string; }typePartialUser= Partial<User>// 相当于: type PickUser = { id: number; age: number; }typePickUser= Pick<User,"id"|"age">

现在实现一个需求:筛选出目标接口中的函数属性,删除其他属性。

// 目标接口interfacePart{
  id:number
  name:string
  subparts: Part[]firstFn:(brand:string)=>void,anotherFn:(channel:string)=>string}

首先遍历接口,将非函数类型的属性设置为

never

,如果是函数类型,取其属性名,然后通过

Pick

拿到函数类型成员集合:

typeFunctionFilterNames<T>={[KinkeyofT]:T[K]extendsFunction?K:never}[keyofT]typeFunctionProperties<T>= Pick<T, FunctionPropertyNames<T>>

完整代码:

typeFunctionPropertyNames<T>={[KinkeyofT]:T[K]extendsFunction?K:never}[keyofT]typeFunctionProperties<T>= Pick<T, FunctionPropertyNames<T>>interfacePart{
  id:number
  name:string
  subparts: Part[]firstFn:(brand:string)=>void,anotherFn:(channel:string)=>string}// 过滤出所有的函数key// type FnNames = "firstFn" | "anotherFn"typeFnNames= FunctionPropertyNames<Part>// 根据对象的key获取函数接口集合// type FnProperties = {//    firstFn: (brand: string) => void;//    anotherFn: (channel: string) => string;// }typeFnProperties= FunctionProperties<Part>let func: FnProperties ={firstFn:function(brand:string):void{thrownewError("Function not implemented.")},anotherFn:function(channel:string):string{thrownewError("Function not implemented.")}}

如果需要深 Partial 我们可以通过泛型递归来实现

typeDeepPartial<T>=TextendsFunction?T:Textendsobject?{[PinkeyofT]?: DeepPartial<T[P]>}:TtypePartialObject= DeepPartial<object>

1.2 Record

先看看

Record

工具的源码:

/**
 * Construct a type with a set of properties K of type T
 */typeRecord<Kextendskeyofany,T>={[PinK]:T;};

从源码和注释来看,这个工具的目标是:**以K中的每个属性作为

key

值,以T作为

value

构建一个

map

结构**

比如:

typepets='dog'|'cat';interfaceIPetInfo{
    name:string,
    age:number,}typeIPets= Record<pets, IPetInfo>;const animalsInfo: IPets ={
    dog:{
        name:'Ryuko',
        age:1},
    cat:{
        name:'Ryuko',
        age:2}}

这个案例来源于这篇文章

现在实现一个需求,封装一个http请求:

首先思考请求方法一般需要哪些参数,比如请求类型、data数据、config配置

通过

enum

枚举几个常见的请求类型,然后每个具体的方法返回值都是一个Promise:

enum IHttpMethods {GET='get',POST='post',DELETE='delete',PUT='put',}interfaceIHttpFn<T=any>{(url:string, config?: AxiosRequestConfig):Promise<T>}// 以enum参数为key,每个key对应一种请求方法// type IHttp = {//   get: IHttpFn<any>;//   post: IHttpFn<any>;//   delete: IHttpFn<any>;//   put: IHttpFn<any>;// }typeIHttp= Record<IHttpMethods, IHttpFn>;

接下来设置一个methods数组,稍后通过

reduce

方法遍历这个数组,目的是将所有的方法体放在一个对象

httpMethods

中,形式如下:

httpMethods ={get:[Function()],post:[Function()],delete:[Function()],put:[Function()]}

最后将

httpMethods

暴露出去,那么外面就可以通过

httpMethods.get(...)

等方法直接调用:

const methods =["get","post","delete","put"];// map为total对象,method为当前遍历到的方法const httpMethods: IHttp = methods.reduce((map:any, method:string)=>{
        map[method]=(url:string, options: AxiosRequestConfig ={...})=>{const{ data,...config }= options;  \
               return(axios asany)[method](url, data, config).then((res: AxiosResponse)=>{if(res.data.errCode){//todo something}else{//todo something}});}},{})exportdefault httpMethods

完整代码:

enum IHttpMethods {GET='get',POST='post',DELETE='delete',PUT='put',}interfaceIHttpFn<T=any>{(url:string, config?: AxiosRequestConfig):Promise<T>}typeIHttp= Record<IHttpMethods, IHttpFn>;const methods =["get","post","delete","put"];const httpMethods: IHttp = methods.reduce((map:any, method:string)=>{
        map[method]=(url:string, options: AxiosRequestConfig ={...})=>{const{ data,...config }= options;  \
               return(axios asany)[method](url, data, config).then((res: AxiosResponse)=>{if(res.data.errCode){//todo something}else{//todo something}});}},{})exportdefault httpMethods

1.3 Exclude & omit

先看看

Exclude

omit

工具的源码:

typeExclude<T,U>=TextendsU?never:T;typeOmit<T,Kextendskeyofany>= Pick<T, Exclude<keyofT,K>>;

从代码和注释来看:

  • Exclude可以选出T不存在于U中的类型
  • Omit可以抛弃某对象中不想要的属性

比如:

// 相当于: type A = 'a'typeA= Exclude<'x'|'a','x'|'y'|'z'>interfaceUser{
  id:number;
  age:number;
  name:string;};// 相当于: type PickUser = { age: number; name: string; }typeOmitUser= Omit<User,"id">

举个例子,现在我们想引入第三方库中的组件,可以这样做:

// 获取参数类型
import { Button } from 'library' // 但是未导出props type
type ButtonProps = React.ComponentProps<typeof Button> // 获取props
type AlertButtonProps = Omit<ButtonProps, 'onClick'> // 去除onClick
const AlertButton: React.FC<AlertButtonProps> = props => (
  <Button onClick={() => alert('hello')} {...props} />
)

二、类型 “string” 没有调用签名 ts(2349)

函数返回元组的时候,在使用的时候,元素可能是元组中的任意一个类型,比如:

在这里插入图片描述

所以,在对返回的元组进行取值操作时,返回值内的类型顺序,可能和函数内的顺序不一致,需要多加一个条件判断:

functiontest<T>(name:T){let myName = name
    const setName =(newName:T):void=>{if(typeof newName ==='string'){console.log(newName.length);}}// console.log(typeof setName);  // functionreturn[myName, setName]}const[myName, setName]=test<string>("Ryuko")// 此表达式不可调用。"string | ((newName: string) => void)" 类型的部分要素不可调用。// 类型 "string" 没有调用签名。ts(2349)// setName("test")// 编译器无法判断setName是string还是一个函数,所以需要通过typeof手动判断if(typeof setName ==='function'){setName("test")}console.log(myName);//Ryukoexport{}

在这个报错案例中,第四行的

typeof newName === 'string'

判断也是很重要的知识点,面对联合类型传参的情况,我们常常需要通过类型判断来决定最后要执行哪个方法:

typeName=stringtypeNameResolve=(name:string)=>stringtypeNameOrResolver= Name | NameResolve

functiongetName(param: NameOrResolver): Name{if(typeof param ==='string'){return param
  }else{returnparam("Ryuko")}}console.log(getName("Ryuko"));// Ryukoconsole.log(getName((p:string)=>{return p +"si"}));// Ryukosi

三、类型 “string” 到类型 “number” 的转换可能是错误的ts(2352)

// 类型 "string" 到类型 "number" 的转换可能是错误的,因为两种类型不能充分重叠。// 如果这是有意的,请先将表达式转换为 "unknown"// 在那些将取得任意值,但不知道具体类型的地方使用 unknown,而非 any。// let a = 'Ryuko' as number// 更正:先将数据转化为unknown,再将数据转化为子类型的numberlet a =('Ryuko'asunknown)asnumberexport{}

这样的转换方式还可以用来定义

html

元素,比如我们想要通过dom操作,来改变某个超链接的url路径地址:

在这里插入图片描述

可是在

HTMLElement

元素节点中并不存在

src

这一属性:

在这里插入图片描述

因此,我们可以将这个节点属性断言转化为子属性

HTMLImageElement

,在子属性身上可以获取到src属性

let elem = document.getElementById('id')as HTMLImageElement

四、类型“string”的参数不能赋给类型“Method”的参数。ts(2345)

typeMethod='get'|'post'|'delete'const requestConfig ={
  url:'localhost: 3000',// config 中的 method 是string类型的菜蔬,而 request 方法中的Method参数// method: 'get'// 解决办法 通过断言进行转换
  method:'get'as Method
}functionrequest(url:string, method: Method){console.log(method);}// 类型“string”的参数不能赋给类型“Method”的参数。ts(2345)request(requestConfig.url, requestConfig.method)export{}

4.1 相关案例

这里再介绍一种情况:

注意:这个用法并没有报错

typeEventNames='click'|'scroll'|'mousemove';functionhandleEvent(ele: Element, event: EventNames){console.log(event);}handleEvent(document.getElementById("app")!,"click")handleEvent(document.getElementById("app")!,"mousemove")

在这个案例中,你可能会认为我传递过去的"click",以及"mousemove"是字符串,既然是字符串,就应该报错:

类型“string”的参数不能赋给类型“EventNames”的参数。ts(2345)

事实上,**这里的字符串参数会被推导为

EventNames

类型,而在前面的错误案例中,method:get将会被推导为

string

类型!这也是为什么在错误案例中,我们需要手动声明类型method: ‘get’ as Method:**

五、对象可能为“未定义”。ts(2532)

functionadd(num1: number, num2?: number): number{// 通过可选链提前知道:可能用不上num2这个变量// 但是如果真的想要操作 num2 的值便会报错return num1 + num2
}
console.log(add(10));export{}

在这时就可以通过

??

来设置默认值

functionadd(num1: number, num2?: number): number{return num1 +(num2 ??0)}
console.log(add(10));export{}

六、“number”索引类型“number”不能分配给“string”索引类型“string”。ts(2413)

在设置索引的时候可能会出现这样的问题:

interfacePerson{[name:string]:string// “number”索引类型“number”不能分配给“string”索引类型“string”[age:number]:number}// 而只要这样写就不会报错了interfacePerson{[name:string]:string|number[age:number]:number}

分析:

在报错的代码中,定义了一个Person接口,这个接口可以采用

字符 & 数字

两种类型的索引:既要符合字符,也要符合数字类型

  • number类型索引表示:类型规范的是一个数组
  • string类型索引表示的是:接收一个对象

数组类型的数据一定可以转化为对象,例如:

['a','b','c']// 等价于{1:'a',2:'b',3:'c'}

而对象类型数据不一定可以转化为数组,例如,如果对象的key值是字符串类型,就无法完成转换了

因此:数组类型可以看作是对象类型的一种子集,例如:

interfaceok{[name:string]:string|number[age:number]:number}interfaceok{[name:string]:string|number|boolean[age:number]:number}interfaceok{[name:string]:number[age:number]:number}interfacenope{[name:string]:number[age:number]:number|string}

在这里同样也说明了,为什么可以通过字符串索引来表示json格式的数据:因为json数据的key本质上就是字符串

typePerson{
    name:string
    age:number}interfaceIPerson{[name:string]: Person
}let p: IPerson ={'Ryuko':{
        name:'Ryuko',
        age:1}}

总结:当使用 number 来索引时,JavaScript 会将它转换成 string 然后再去索引对象。也就是说用 1(一个number)去索引等同于使用”1″(一个string)去索引,因此我们可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。

6.1 相关案例

// okinterfaceFoo{[index:string]:number;
  x:number;
  y:number;}// wronginterfaceBar{[index:string]:number;
  x:number;// 类型“string”的属性“y”不能赋给“string”索引类型“number”。ts(2411)
  y:string;}

接口Bar中采用了

string

类型索引,所以内部属性可以写为

x,y,z,xxxx...

等字符串,他们的值都应该声明为number类型。

更正方法:

// okinterfaceBar{[index:string]:number;
  x:number;// 保证和索引数据一致
  y:number;}// okinterfaceBar{[index:string]:number|string;
  x:number;
  y:string;}

七、对象的类型为 “unknown”。ts(2571)

在一些兑换码场景,经常会需要将兑换码全部转为大写,之后再进行判断:

functionisString(s:unknown):boolean{returntypeof s ==='string'}functiontoUpperCase(x:unknown){if(isString(x)){// 对象的类型为 "unknown"。ts(2571)
    x.toUpperCase()}}

可是在上一行明明已经通过

isString()

函数确认参数 x 为 string 类型了啊?

原因在于:即使第六行进行了字符串判断,在初次类型检查的时候,编译器看到第七行的

x.toUpperCase() 

,仍会判定x是

unkown

类型。

解决办法:采用

is

关键字。

is

关键字一般用于函数返回值类型中,判断参数是否属于某一类型,并根据结果返回对应的布尔类型。通过 is 关键字将函数参数类型范围缩小为 string 类型

functionisString(s:unknown): s isstring{returntypeof s ==='string'}functiontoUpperCase(x:unknown){if(isString(x)){
    x.toUpperCase()}}

八、类型“T”上不存在属性“length”。ts(2339)

在遇到泛型的时候,有时候我们需要传递一个字符串给函数,但是对于函数来说,参数a仅仅是一个T类型数据,编译器并不知道参数a身上有length属性。

// 类型“T”上不存在属性“length”。ts(2339)functiontest<T>(a:T){console.log(a.length);}

所以在这个时候可以考虑使用

类型约束

来解决该问题:

interfacelengthConfig{
  length:number}// 类型“T”上不存在属性“length”。ts(2339)functiontest<Textends lengthConfig>(a:T){console.log(a.length);}

九、成员 “T” 隐式具有 “any” 类型,但可以从用法中推断出更好的类型。ts(7045)

目前来看,遇到这种错误的时候只能说多试试…

以这个例子来说,Person接口的函数返回类型声明为T会报错,但ArrayFunc接口不会

interfacePerson<T,U>{
  name:T,
  age:U,// 应为“=>”。ts(1005)say:()=>T}interfaceArrayFunc<T>{(length:number, value:T):T[]}// 如果已经声明了函数类型,在定义函数的时候不必声明参数类型const createArrayFunc: ArrayFunc<string>=(length, value)=>{return['1','2']}

十、不能将类型“() => string”分配给类型“Func”。ts(2322)

interfaceFunc{print():string}// 不能将类型“() => string”分配给类型“Func”。ts(2322)const helloFuncWrong:Func=function(){return"Ryuko"}// 如果一定要添加函数名,那么就应该对照着接口中的模式来写const helloFuncOk: Func ={print(){return"Ryuko"}}

一般情况下,函数式接口中,(其实type类型也是一样),不能添加具体的函数名:

interfaceFunc{():string}const helloFunc:Func=function(){return"Ryuko"}

10.1 相关案例

其实关于2322报错,主要可以理解为:设定了某个类型的数据,但是你在使用的时候却为他赋值为其他类型的数据

let arr:{id:number}[]=[]// ok
arr.push({id:1})// wrong
arr.push([{id:2, age:1}])

以这个来说,定义的arr为包含着id对象的数组,可是第七行却赋值为包含{ id,age }类型的对象数组,现在有两种解决办法:

  1. 改变第一行arr类型定义
  2. 数据可以由大赋值给小的理念来看,我们可以将any类型的数据赋值给arr对象:// okarr =[{id:2, age:1}asany]

十一、对象的类型为 “unknown”。ts(2571)

在请求数据的时候,我们常常会有这样的操作:

<script lang='ts' setup>import{ axiosGet }from'@/utils/http'import{ reactive }from'vue'let state =reactive({})asyncfunctiongetImgList(){let result =awaitaxiosGet("/api/getlunbo")+result.status ===0? state.imgList = result.message :""}getImgList()</script>

如果这段代码没有采用ts类型检测,可以顺利运行,流程为:向接口发起请求,将接口返回的数据赋值给state.imgList。

但是,这里有ts类型检测,请求结果返回的promise.then的结果并没有任何类型声明,所以编译器并不知道result身上存在什么属性,于是发出报错:对象(result)的类型为unknown。

这时候我们就需要手动来为返回值设置类型了,先看看接口格式:

{status:0,message:[{url:"http://www.baidu.com",img:"http://img2.imgtn.bdimg.com/it/u=500808421,1575925585&fm=200&gp=0.jpg"},{url:"http://www.qq.com",img:"http://p16.qhimg.com/bdr/__85/d/_open360/fengjing34/ABC8cbd.jpg"}]}

根据接口类型,在

type.d.ts

类型声明文件中定义,然后在代码中为返回值设置类型转换:

interfaceResultType{
  status:number,
  message:Array<any>}declarenamespaceHOME{interfaceStateType{
      lunboList:{
          img?:string,
          url?:string}[]}}

接下来只需要为数据添加类型就可以了:

  1. 将result返回值数据手动断言为ResultType类型
  2. 为state对象中的数据绑定对象成员类型
<script lang='ts' setup>import{ axiosGet }from'@/utils/http'import{ reactive }from'vue'let state =reactive<HOME.StateType>({
  lunboList:[]})asyncfunctiongetImgList(){let result =awaitaxiosGet("/api/getlunbo")as ResultType
  // 需要注意的是,这里的result.message是any类型的数组// 可以直接将 any[] 赋值给 HOME.StateType 下的lunboList[]// 这是因为 any 类型的数据可以赋值给任意类型,因为**多属性数据可以赋值给少属性数据**
  result.status ===0? state.lunboList = result.message :""}getImgList()</script>

参考文章

TypeScript 高级技巧
TypeScript 联合类型


本文转载自: https://blog.csdn.net/flow_camphor/article/details/125695085
版权归原作者 黑猫几绛 所有, 如有侵权,请联系我们删除。

“TypeScript 报错汇总”的评论:

还没有评论