首页IT科技typescript then(TypeScript 报错汇总)

typescript then(TypeScript 报错汇总)

时间2025-04-29 21:02:54分类IT科技浏览8112
导读:TypeScript 报错汇总 在这篇文章中将记录我遇到的ts错误,应该会持续更新。...

TypeScript 报错汇总

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

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

一             、内置工具

1.1 Pick & Partial

先看看Pick和Partial工具的源码:

type Partial<T> = { [P in keyof T]?: T[P]; }; type Pick<T, K extends keyof T> = { [P in K]: T[P]; };

从代码和注释来看       ,

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

比如:

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

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

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

首先遍历接口      ,将非函数类型的属性设置为never                    ,如果是函数类型             ,取其属性名      ,然后通过Pick拿到函数类型成员集合:

type FunctionFilterNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>

完整代码:

type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>> interface Part { id: number name: string subparts: Part[] firstFn: (brand: string) => void, anotherFn: (channel: string) => string } // 过滤出所有的函数key // type FnNames = "firstFn" | "anotherFn" type FnNames = FunctionPropertyNames<Part> // 根据对象的key获取函数接口集合 // type FnProperties = { // firstFn: (brand: string) => void; // anotherFn: (channel: string) => string; // } type FnProperties = FunctionProperties<Part> let func: FnProperties = { firstFn: function (brand: string): void { throw new Error("Function not implemented.") }, anotherFn: function (channel: string): string { throw new Error("Function not implemented.") } }

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

type DeepPartial<T> = T extends Function ? T : T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T type PartialObject = DeepPartial<object>

1.2 Record

先看看Record工具的源码:

/** * Construct a type with a set of properties K of type T */ type Record<K extends keyof any, T> = { [P in K]: T; };

从源码和注释来看                    ,这个工具的目标是:以K中的每个属性作为key值             ,以T作为value构建一个map结构

比如:

type pets = dog | cat; interface IPetInfo { name: string, age: number, } type IPets = 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, } interface IHttpFn<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>; // } type IHttp = 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 as any)[method](url, data, config) .then((res: AxiosResponse) => { if (res.data.errCode) { //todo something } else { //todo something } }); } },{} ) export default httpMethods

完整代码:

enum IHttpMethods { GET = get, POST = post, DELETE = delete, PUT = put, } interface IHttpFn<T = any> { (url: string, config?: AxiosRequestConfig): Promise<T> } type IHttp = 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 as any)[method](url, data, config) .then((res: AxiosResponse) => { if (res.data.errCode) { //todo something } else { //todo something } }); } },{} ) export default httpMethods

1.3 Exclude & omit

先看看Exclude和omit工具的源码:

type Exclude<T, U> = T extends U ? never : T; type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

从代码和注释来看:

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

比如:

// 相当于: type A = a type A = Exclude<x | a, x | y | z> interface User { id: number; age: number; name: string; }; // 相当于: type PickUser = { age: number; name: string; } type OmitUser = 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)

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

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

function test<T>(name: T){ let myName = name const setName = (newName: T): void => { if(typeof newName === string){ console.log(newName.length); } } // console.log(typeof setName); // function return [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); //Ryuko export{}

在这个报错案例中,第四行的typeof newName === string判断也是很重要的知识点             ,面对联合类型传参的情况                    ,我们常常需要通过类型判断来决定最后要执行哪个方法:

type Name = string type NameResolve = (name: string) => string type NameOrResolver = Name | NameResolve function getName(param: NameOrResolver): Name{ if(typeof param === string){ return param }else{ return param("Ryuko") } } console.log(getName("Ryuko")); // Ryuko console.log(getName( (p: string) => { return p + "si" } )); // Ryukosi

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

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

这样的转换方式还可以用来定义html元素                    ,比如我们想要通过dom操作             ,来改变某个超链接的url路径地址:

可是在HTMLElement元素节点中并不存在src这一属性:

因此      ,我们可以将这个节点属性断言转化为子属性HTMLImageElement                    ,在子属性身上可以获取到src属性

let elem = document.getElementById(id) as HTMLImageElement

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

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

4.1 相关案例

这里再介绍一种情况:

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

type EventNames = click | scroll | mousemove; function handleEvent(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)

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

在这时就可以通过??来设置默认值

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

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

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

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

分析:

在报错的代码中                    ,定义了一个Person接口       ,这个接口可以采用字符 & 数字两种类型的索引:既要符合字符      ,也要符合数字类型

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

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

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

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

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

interface ok { [name: string] : string | number [age: number] : number } interface ok { [name: string] : string | number | boolean [age: number] : number } interface ok { [name: string] : number [age: number] : number } interface nope { [name: string] : number [age: number] : number | string }

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

type Person{ name: string age: number } interface IPerson{ [name: string]: Person } let p: IPerson = { Ryuko: { name: Ryuko, age: 1 } }

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

6.1 相关案例

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

接口Bar中采用了string类型索引             ,所以内部属性可以写为x,y,z,xxxx...等字符串                    ,他们的值都应该声明为number类型                    。

更正方法:

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

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

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

function isString(s: unknown): boolean { return typeof s === string } function toUpperCase(x: unknown) { if(isString(x)) { // 对象的类型为 "unknown"             。ts(2571) x.toUpperCase() } }

可是在上一行明明已经通过 isString() 函数确认参数 x 为 string 类型了啊?

原因在于:即使第六行进行了字符串判断                    ,在初次类型检查的时候       ,编译器看到第七行的x.toUpperCase()       ,仍会判定x是unkown类型                    。

解决办法:采用is关键字       。

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

function isString(s: unknown): s is string { return typeof s === string } function toUpperCase(x: unknown) { if(isString(x)) { x.toUpperCase() } }

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

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

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

所以在这个时候可以考虑使用类型约束来解决该问题:

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

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

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

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

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

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

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

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

interface Func{ (): 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 }类型的对象数组                    ,现在有两种解决办法:

改变第一行arr类型定义

数据可以由大赋值给小的理念来看             ,我们可以将any类型的数据赋值给arr对象:

// ok arr = [{id:2, age: 1} as any]

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

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

<script lang=ts setup> import { axiosGet } from @/utils/http import { reactive } from vue let state = reactive({}) async function getImgList() { let result = await axiosGet("/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类型声明文件中定义      ,然后在代码中为返回值设置类型转换:

interface ResultType { status: number, message: Array<any> } declare namespace HOME { interface StateType { lunboList: { img?: string, url?: string }[] } }

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

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

参考文章

TypeScript 高级技巧

TypeScript 联合类型

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
整站网站优化推荐(网站整站优化公司) 微信小程序顶部导航栏背景图片(uniapp开发微信小程序自定义顶部导航栏)