TypeScript error summary

TypeScript error summary

I will record the ts errors I encountered in this article, which should be updated continuously.

Sometimes it seems to be a good choice to learn from the wrong point, so you are welcome to private message me some ts questions.

1. Built-in tools

1.1 Pick & Partial

First look at Pickthe Partialsource code of the tool:

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

From the code and comments,

  • Use Pickthe tool to filter the attributes in the generic T according to the joint type data ,
  • Use Partialtools to make interface attributes optional

for example:

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">

Now implement a requirement: filter out the function attributes in the target interface and delete other attributes.

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

First traverse the interface, set the non-function type attribute to never, if it is a function type, take its attribute name, and then Pickget the function type member set:

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

type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>

Full code:

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.")
  }
}

If we need deep Partial, we can achieve it through generic recursion

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

First look at Recordthe source code of the tool:

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

Judging from the source code and comments, the goal of this tool is to use each attribute in K as keya value and T as a structure to valuebuildmap

for example:

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
    }
}

This case comes from this article

Now implement a requirement and encapsulate an http request:

First think about what parameters are generally required for the request method, such as request type, data data, config configuration

By enumenumerating several common request types, each specific method returns a 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>;

Next, set up a methods array, and later reducetraverse the array through the method, the purpose is to put all the method bodies in one object httpMethods, the form is as follows:

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

Finally, it will httpMethodsbe exposed, and then the outside can httpMethods.get(...)be called directly by other methods:

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

Full code:

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

First look at Excludethe omitsource code of the tool:

type Exclude<T, U> = T extends U ? never : T;

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

From the code and comments:

  • Exclude can select types that T does not exist in U
  • Omit can discard unwanted properties in an object

for example:

// 相当于: 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">

For example, now we want to introduce components in third-party libraries, we can do this:

// 获取参数类型
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} />
)

2. Type "string" does not call signature ts(2349)

When the function returns a tuple, the element may be any type in the tuple when used, for example:

insert image description here

Therefore, when performing value operations on the returned tuple, the order of types in the return value may be inconsistent with the order in the function, and an additional conditional judgment is required:

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{
    
    }

In this error reporting case, typeof newName === 'string'the judgment in the fourth line is also a very important knowledge point. In the case of joint type parameter passing, we often need to use type judgment to determine which method to execute in the end:

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

3. Conversion of type "string" to type "number" may be wrong ts(2352)

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

// 更正:先将数据转化为unknown,再将数据转化为子类型的number
let a = ('Ryuko' as unknown) as number

export {
    
    }

This conversion method can also be used to define htmlelements. For example, we want to change the url path address of a hyperlink through dom operations:

insert image description here

However, this attribute HTMLElementdoes not exist in the element node :src

insert image description here

Therefore, we can convert this node attribute assertion into a sub-attribute HTMLImageElement, and the src attribute can be obtained from the sub-attribute

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

4. A parameter of type "string" cannot be assigned to a parameter of type "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 Related cases

Here is another situation:

Note : This usage does not report an error

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

In this case, you may think that the "click" and "mousemove" I passed in the past are strings. Since they are strings, an error should be reported: 类型“string”的参数不能赋给类型“EventNames”的参数。ts(2345).

In fact, the string parameter here will be deduced as EventNamesa type , while in the previous error case, method: get will be deduced as stringa type! That's why, in the error case, we need to manually declare the type method: 'get' as Method:

Five, the object may be "undefined". ts(2532)

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

At this time, you can ??set the default value by

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

6. 'number' index type 'number' is not assignable to 'string' index type 'string'. ts(2413)

There may be such problems when setting the index:

interface Person {
    
    
  [name: string] : string
  // “number”索引类型“number”不能分配给“string”索引类型“string”
  [age: number] : number
}

// 而只要这样写就不会报错了
interface Person {
    
    
  [name: string] : string | number
  [age: number] : number
}

analyze:

In the code that reported the error, a Person interface is defined, which can use 字符 & 数字two types of indexes: both character and numeric types

  • The number type index indicates that the type specification is an array
  • The string type index means: receive an object

The data of the array type must be converted into an object, for example:

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

However, object type data may not necessarily be converted into an array. For example, if the key value of the object is a string type, the conversion cannot be completed.

Therefore : array types can be seen as a subset of object types, for example:

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
}

It also explains here why data in json format can be represented by a string index: because the key of json data is essentially a string

type Person{
    
    
	name: string
	age: number
}

interface IPerson{
    
    
	[name: string]: Person
}

let p: IPerson = {
    
    
	'Ryuko': {
    
    
		name: 'Ryuko',
		age: 1
	}
}

Summary : When using a number to index, JavaScript will convert it to a string and then index the object. That is to say, using 1 (a number) to index is equivalent to using "1" (a string) to index, so we can use both types of indexes at the same time, but the return value of the number index must be the return value type of the string index Subtype.

6.1 Related Cases

// 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; 
}

Type index is used in interface Bar string, so internal properties can be written as x,y,z,xxxx...equal strings, and their values ​​should be declared as number type.

Correction method:

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

// ok
interface Bar {
    
    
  [index: string]: number | string;
  x: number;
  y: string; 
}

7. The type of the object is "unknown". ts(2571)

In some redemption code scenarios, it is often necessary to convert all the redemption codes to uppercase before making a judgment:

function isString(s: unknown): boolean {
    
    
  return typeof s === 'string'
}

function toUpperCase(x: unknown) {
    
    
  if(isString(x)) {
    
    
    // 对象的类型为 "unknown"。ts(2571)
    x.toUpperCase() 
  }
}

But in the previous line, it is clearly confirmed that the parameter x is of string type through isString()the function ?

The reason is: even if the string judgment is performed on the sixth line, when the compiler sees the seventh line during the initial type check, it x.toUpperCase() will still determine that x is unkowna type.

The solution: use iskeywords.

isKeywords are generally used in the return value type of a function to determine whether the parameter belongs to a certain type, and return the corresponding Boolean type according to the result. Use the is keyword to narrow the range of function parameter types to string types

function isString(s: unknown): s is string {
    
    
  return typeof s === 'string'
}

function toUpperCase(x: unknown) {
    
    
  if(isString(x)) {
    
    
    x.toUpperCase() 
  }
}

8. Property 'length' does not exist on type 'T'. ts(2339)

When encountering generics, sometimes we need to pass a string to the function, but for the function, the parameter a is just a T type data, and the compiler does not know that the parameter a has a length attribute.

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

So at this time, you can consider using 类型约束to solve this problem:

interface lengthConfig{
    
    
  length: number
}

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

9. Member "T" implicitly has type "any", but a better type can be inferred from usage. ts(7045)

At present, when encountering this kind of error, you can only say try more...

In this example, if the function return type of the Person interface is declared as T, an error will be reported, but the ArrayFunc interface will not

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']
}

10. Type '() => string' is not assignable to type 'Func'. ts(2322)

interface Func{
    
    
  print(): string
}

// 不能将类型“() => string”分配给类型“Func”。ts(2322)
const helloFuncWrong: Func = function(){
    
    
  return "Ryuko"
}

// 如果一定要添加函数名,那么就应该对照着接口中的模式来写
const helloFuncOk: Func = {
    
    
  print() {
    
    
    return "Ryuko"
  }
}

In general, in a functional interface (in fact, the type type is the same), you cannot add a specific function name:

interface Func{
    
    
  (): string
}

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

10.1 Related Cases

In fact, the 2322 error can be mainly understood as: a certain type of data is set, but you assign it to other types of data when you use it

let arr: {
    
    id: number}[] = []

// ok
arr.push({
    
    id: 1})

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

In this case, the defined arr is an array containing id objects, but the seventh line is assigned an object array containing {id,age} type. Now there are two solutions:

  1. Change the first line of arr type definition

  2. From the idea that data can be assigned from large to small , we can anyassign data of type to the arr object:

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

11. The type of the object is "unknown". ts(2571)

When requesting data, we often have operations like this:

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

If this code does not use ts type detection, it can run smoothly. The process is: initiate a request to the interface, and assign the data returned by the interface to state.imgList.

However, there is ts type detection here. The result of promise.then returned by the request result does not have any type declaration , so the compiler does not know what attributes exist on the result, so it issues an error: the type of the object (result) is unknown.

At this time, we need to manually set the type for the return value, first look at the interface format:

{
    
    
    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"
        }
   ]
}

According to the interface type, type.d.tsdefine it in the type declaration file, and then set the type conversion for the return value in the code:

interface ResultType {
    
    
  status: number,
  message: Array<any>
}

declare namespace HOME {
    
    
  interface StateType {
    
    
      lunboList: {
    
    
          img?: string,
          url?: string
      }[]
  }
}

Next, just add a type to the data:

  1. Manually assert the result return value data as ResultTypea type
  2. For the data binding object member type in the state object
<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>

reference article

Advanced TypeScript Tips
TypeScript Union Types

Guess you like

Origin blog.csdn.net/flow_camphor/article/details/125695085