TypeScript 报错汇总

Posted 黑猫几绛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TypeScript 报错汇总相关的知识,希望对你有一定的参考价值。

TypeScript 报错汇总

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

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

一、内置工具

1.1 Pick & Partial

先看看PickPartial工具的源码:

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

先看看Excludeomit工具的源码:

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':  

  下载地址:百度网盘下载



以上是关于TypeScript 报错汇总的主要内容,如果未能解决你的问题,请参考以下文章

如何在 TypeScript 中为 css 模块设置汇总

typescript eventTarget dataset 如何修复报错?

Egret项目Typescript的编译报错

Vue+typescript报错项

TypeScript创建泛型类报错(泛型类)

TypeScript创建泛型类报错(泛型类)