• ts泛型,映射,条件类型和类型提取infer和一些常用工具库的说明


    Typescript当中的T,K,V到底是个啥

    • 有时候,我们看到下面的代码,当然,这里是简单例子来说
    function identity <T> (value:T) : T {
    	return value;
    }
    
    • 1
    • 2
    • 3
    • 其实泛型就是使用字母来代替将要接收的类型,这里的"T"是代表类型的缩写,表示对将要接收类型的一个占位符,占位符可以是任意字母,下面是一些常用的占位符

      • T(Type) 表示类型
      • K(Key) 表示对象中键的类型
      • V(value) 表示对象中值的类型
      • E(Element) 表示元素类型
    • 如果在函数中使用了泛型,那么我们可以在使用的时候指明类型,也可以不显式指明类型

    function identity <T , U>(value: T ,message: U) : T {
        console.log(message)
        return value;
    }
    //不指定泛型变量的实际类型
    console.log(identity(20,'动感超人'));
    
    //手动指定泛型变量的实际类型
    console.log(identity<number,string>(20,'动感超人'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    declare

    • 假如我们在html当中引入了jquery插件,那么就会在全局当中增加一个关键字$,此时如果我们在ts文件当中的书写关键字$

    • 就会发现ts会提示找不到$,ts2304,也就是说ts不认识这个全局变量$

    • 所以我们可以使用declare来定义这个全局变量declare const $ = xxxx,这样子ts就认识这个全局变量$了

    • 注意点

      • declare声明不包含具体的实现,也就是说我们只是声明,不做具体处理
      • declare可以定义全局变量,全局函数,全局枚举,全局类等
      • 你看到的xxx.d.ts就是用于放置声明文件的
    • declare和一些声明文件查询:@地址

    疑问

    • 为什么我们可以在ts中直接写Math,JSON,Object这些全局对象呢?那是英文typescript已经在文件当中声明了
    • 在vie当中,我们可以在node_modules/vite/client.d.ts当中就可以看到declare的声明,部分代码如下
    declare module '*.module.css' {
      const classes: CSSModuleClasses
      export default classes
    }
    // CSS
    declare module '*.css' {
      const css: string
      export default css
    }
    // images
    declare module '*.jpg' {
      const src: string
      export default src
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • vite为什么要这样子做呢,因为如果它不这样子做,那么我们在使用下面代码就会报错
    //如果vite不declare,那么就会提示找不到模块"./file.css"/或其相应的类型声明
    import css from "./file.css";
    
    //如果vite不declare,那么就会提示找不到模块"./abao.jpg"或其相应的类型声明
    import logo from "./abao.jpg";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 从ts2.0开始,declare支持通配符了,就如上声明一样使用了通配符

    any类型和unknown类型

    • any:我不在乎它的类型

    • unknown:我不知道它的类型(可以理解为类型安全的any),使用了unknown,必须要自己进行类型检测后才可以对变量进行操作,否则会报警告或错误

    • 对于下列函数,如果是使用any类型,是不会有任何报错提示的

    function invokeCallBack(callback:any){
        try {
            callback();
        }catch (e){
            console.log(e)
        }
    }
    invokeCallBack(1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 而对于我们使用了unknown,则会提示TS2571: Object is of type 'unknown'.
    function invokeCallBack(callback:unknown){
        try {
            //TS2571: Object is of type 'unknown'.
            callback();
        }catch (e){
            console.log(e)
        }
    }
    invokeCallBack(1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 所以对于unknown来说,我们在使用前就必须要判断是否可以执行后才可以没有警告
    function invokeCallBack(callback:unknown){
        try {
            if(typeof callback === 'function'){
                callback();
            }
        }catch (e){
            console.log(e)
        }
    }
    invokeCallBack(1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 需要注意的是,unknown类型的变量只可以赋值给unknown类型或者是any类型

    typescript当中的类型

    • never是空集,所以never类型无法被其他类型所赋值
    //Type 'string' is not assignable to type 'never'
    let num: never = 123;
    
    //Type 'string' is not assignable to type 'never'
    let name: never = '超人';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • interface接口定义对象类型,可以使用extends进行扩展
    interface Vector1D {x:number}
    
    //等同于
    	interface Vector3D {
    		x:number,
    		y:number,
    	}
    interface Vector2D extends Vector1D {y:number}
    
    //等同于
    	interface Vector3D {
    		x:number,
    		y:number,
    		z:number
    	}
    interface Vector3D extends Vector2d {z:number};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    type和interface的异同

    前置知识

    type 类型别名
    • 针对基本类型和非对象类型的情况非常有用,支持泛型
    interface 接口
    • interface只能定义对象类型
    • 定义接口类型时,可以同时声明对象身上的属性和方法

    相同点

    1. 类型别名和接口都可以用来描述对象或函数
    type Point  = {
      x: number
      y:number
    }
    type SetPoint = (x:number,y:number) => void;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    interface Point {
      x: number;
      y: number; // ";" 或则 "," 或者不写都可以
    }
    interface SetPoint {
      (x: number, y: number): void;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 类型别名和接口都支持扩展
      • 类型别名扩展使用&交叉运算符进行合并运算,注意,交叉类型中的交叉,并不是指两个类型的交集,而是并集
      • 接口扩展使用关键字extends
    //类型别名通过交叉运算符来扩展
    type Animal = {
      name: string;
    };
    type Bear = Animal & {
      honey: boolean;
    };
    const bear: Bear = {
      name: "熊大",
      honey: false,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // 接口通过extends来扩展
    interface Animal {
      name: string;
    }
    interface Bear extends Animal {
      honey: boolean;
    }
    const bear: Bear = {
      name: "熊大",
      honey: false,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 类型别名和接口都支持相互扩展,但需要注意的是接口只支持使用extends关键字来继承,类型也只支持使用&来完成扩展
    type Animal = {
      name: string;
    };
    interface Bear extends Animal {
      honey: boolean;
    }
    const bear: Bear = {
      name: "熊大",
      honey: false,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    interface Animal {
      name: string;
    }
    type Bear = Animal & {
      honey: boolean;
    };
    const bear: Bear = {
      name: "熊大",
      honey: false,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    不同点

    1. 类型别名可以为基本类型,联合类型或元组类型定义别名,接口不行
    type MyNumber = number; //基本类型定义别名
    type StringOrNumber = string | number; //联合类型定义别名
    type Point = [number, number]; //元组类型定义别名
    
    • 1
    • 2
    • 3
    1. 同名接口会自动合并,而类型别名不会
    interface User {
      name: string;
    }
    interface User {
      age: number;
    }
    let user: User = {
      name: "李白",
      age: 1000,
    };
    user.name; //李白
    user.age; //1000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    type User = {
      name: string;
    };
    //标识符“User”重复。ts(2300)
    type User = {
      age: number;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    类型别名和接口的一些使用场景

    • 使用类型别名的场景

      • 定义基本类型的别名时,使用type
      • 定义元组类型时,使用type
      • 定义函数类型时,使用type
      • 定义联合类型时,使用type
      • 定义映射类型时,使用type
    • 使用接口的场景

      • 需要利用接口自动合并特征的时候,使用interface
      • 定义对象类型且无需使用type的时候,调用interface

    索引签名和Record内置工具类型

    • 有时候我们可能会想这样子,我想规定一个对象类型的key只能为字符串,值是任意的,那么要怎么做呢?可以使用索引签名

    • 格式语法如下

      • {[key:KeyType] : ValueType}
      • key: 固定的写法
      • **keyType: ** key的类型,只支持string,number,symbol,不能为字面量类型或者是泛型类型,如需要使用字面量或泛型,则需要使用Record内置工具类型
      • valueType: value的类型
    
    //比如下面
    interface selfName1 {
        [key:string] : string
    }
    const test1:selfName1 = {
       name:'李白',
       hobby:'吃饭',
    }
    
    const selfName2:{[key:string] : string} = {
        name:'李白',
        hobby:'吃饭',
        age:1000,//报错,警告
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 需要注意的是KeyType只能为string 或者 number或者symbol不能为其他的值
    //错误的keyType
    interface selfName3 {
        //keytype只能为string,number,symbol
    	[key:boolean] : string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 使用索引签名也可以和别的已知的key,value使用
    interface Options {
    	[key:string]:string | number | boolean,
    	timeout:number,//已知的键
    }
    const option:Options = {
        timeout:1000,
        errorMessae:'The request timed out!',
        isSuccess:false,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 此外,索引签名也可以和模板字符串使用
    interface PropChangeHandler {
        [key:`${string}Changed`]: () => void;
    }
    
    let handlers:PropChangeHandler = {
        idChanged: () => {},
        nameChanged: () => {},
    
        //报错,因为后面少了一个字符"d",和规定的type不相同
        ageChange: () => {},
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Record内置工具类型和索引签名

    • 索引签名类型参数不能为字面量类型或者是泛型类型
    type User1 = {
    	//报错 索引签名类型参数不能为字面量类型或者是泛型类型
    	[key:"id"]:string,
    }
    
    type User2 = {
    //报错 索引签名类型参数不能为字面量类型或者是泛型类型
    	[key:"id" | "name"] :string 
    }
    
    //有人可能会有疑问,说,哎呀,这个为什么可以
    interface PropChangeHandler {
        //这个没问题,因为这个不是字面量啊
        [key:`${string}Changed`]: () => void;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 而Record却可以包括字面量和泛型
    type User3 = Record<"id", string>
    const a:User3 = {
        id:'2tjawjtiaowt',
    }
    
    type User4 = Record<'id' | 'name', string>;
    const b:User4 = {
        id:'2tjawjtiaowt',
        name:'动感超人'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    原来映射类型是这样子工作的

    • ts的一些工具类型,比如说Pick,就是从某一个类型当中挑选一部分
    • ts中的工具类型操作的是类型,而js当中工具类型操作的是值,
    • 下面用ts的pick用js来做下解释,调用ts工具类型(类似函数)使用的是尖括号,js函数则是小括号(一句话,ts用尖括号,js用小括号)
    //K extends T 泛型约束的语法,用于约束泛型K类型
    type Pick <T,K extends keyof T> = {
    	[P in K]:T[P]
    }
    //用js来解释就是
    function Pick (obj,keys) {
    	const result = {};
    	for(const key of keys){
    		result[key] = obj[key]''
    	}
    	return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    动图

    ts内置工具类型中的keyof操作符有啥用

    • 首先来说下js当中的Object.keys函数作用,Object.keys会返回对象身上所有可枚举key组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。
    const object1 = {
      a: 'somestring',
      b: 42,
      c: false
    };
    
    console.log(Object.keys(object1));
    // expected output: Array ["a", "b", "c"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 那么ts当中的keyof也是,返回对象身上key值组成的联合类型
    class Person {
        year:number = 2022;
        hobby:string = '吃饭';
    }
    //等同于 type P0Types = 'year' | 'hobby'
    type P0Types = keyof Person;
    const P01:P0Types = 'year';
    const P02:P0Types = 'hobby';
    
    
    interface Person1Inter {
        id:number,
        name:string,
    }
    //等同于 type P1Types = 'id' | 'name' 
    type P1Types = keyof Person1Inter;
    const P11:P1Types = 'id';
    const P12:P1Types = 'name';
    
    
    //针对枚举
    enum HttpMethods {
        Get,
        Post,
    }
    type Methods = keyof typeof HttpMethods;
    const P21:Methods = 'Get';
    const P22:Methods = 'Post';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 对原始的数据类型也会获取到对应的联合类型
    type K1 = keyof boolean ;// ValueOf
    
    // "toString" | "toFixed" | "toExponential"
    // | "toPrecision" | "valueOf" | "toLocaleString"
    type K2 = keyof number;
    
    type K3 = keyof any; string | number | symbol
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ts为什么keyof typeof可以拿到枚举的联合类型 ?

    export enum ab {
      'a',
      'b'
    }
    
    //uni等同于 'a' | 'b'
    type uni = keyof typeof ab
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    TypeScript 中分“类型”和“值”,类型是 TypeScript 认的,一般编译后会消失(不存在于 JS 中)。枚举是比较特殊的定义,虽然定义成类型,但实际是值,它在编译成 JS 之后是一个对象。

    TypeScript 中的枚举还分情况,有数值型枚举,也有字符串型枚举,还有混合型的……不讨论复杂了,这里就说数值型的。

    enum Hello {
        A,
        B
    }
    
    type X = keyof Hello;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    你猜 X 是什么呢?你会发现它包含 toFixedtoPrecision 等,是不是感觉像是个 Number 类型的 Key 呢?

    再来看看 Number 类型的 …… 果然一样

    如果不加 Exclude 运算,会看到 keyof Number 看不到键列表

    想想,实际上也是,如果这样使用

    const a: Hello = Hello.A;
    
    • 1

    a 的值实际上是一个 Number(仅数值型枚举的情况)

    所以 TypeScript 中需要使用 typeof Hello 来取实际的枚举类型(不然就是 Number 的子类型),实际上它是一个接口。

    这个类型取出来之后,枚举值名称是被当作类型的 Key 的,所以可以用 keyof 把键值取出来。

    ts的映射和泛型

    • ts的映射个人觉得有点像是js当中的map吧,操作都是传入a,经过处理后返回b

    • 语法:

      • { [P in K]?:T }
    • 一些工具库,比如说Partial,Required,Pick就是通过映射来实现的

    • 比如现在有一个需求,需要把这个类型全部改为可选的,代码如下

    type User = {
    	name: string,
    	password: string,
    	address: string,
    	phone: string,
    }
    
    //里面的属性需要全部改为可选的
    type User1 = {
    	name?:string,
    	password?:string,
    	address?:string,
    	phone?:string,
    }
    //你可以使用工具库当中的Partial,也可以自己写一个~
    type selfPartial<T> = {
    	[K in keyof T]?:T[k]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 在映射的过程当中,可以通过添加+,-来添加.移除修饰符(+(加号为默认))
      • 没有添加前缀,默认使用加号
    • 映射类型示例
    type Item =
    { a: string; 
      b: number; 
      c: boolean 
    };
    
    
    // { x: number,y: number }
    type T1 = { [P in 'x' | 'y']: number };
    
    
    //{ x: 'x',y: 'y }
    type T2 = { [P in 'x' | 'y']: P };
    
    //{a: string,b: number}
    type T3 = { [P in 'a' | 'b']: Item[P] }
    
    //{a: string,b: number,c: boolean}
    type T4 = { [P in keyof Item]: Item[P] }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 映射演示

    映射演示

    ts条件类型Conditional Types

    • ts的条件类型和js当中的三元运算符差不多

    • 还是一句话,ts操作的是类型,js操作的是值

    • 语法

      • T extends U ? X : Y
      • T,U,X,Y都是类型占位符
      • 解释:当类型T可以赋值给类型U的时候,就返回X,否则就返回Y
    • 先来看一个简单的例子

      • type I2 = IsString😕/输出类型为boolean,是因为any这二个值都可以满足,所以就为boolean
    type IsString<T> = T extends string ? true :false;
    
    type I0 = IsString<number>;// false
    
    type I1 = IsString<'abc'>;// true
    type I2 = IsString<any>;// boolean
    type I3 = IsString<never>;// never
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    除了判断单一类型之外,利用条件类型和条件链,我们还可以同时判断多种类型

    • 当传入any的时候,会返回联合类型,因为都符合,所以在ts的三元是一直运算下去的
    type TypeName<T> = 
        T extends string ? string:
        T extends number ? number:
        T extends boolean ? boolean:
        T extends undefined ? undefined:
        T extends Function ? Function:
        Object;
    type T0 = TypeName<string> //string
    type T1 = TypeName<'a'> //string
    type T2 = TypeName<true> //boolean
    type T3 = TypeName<() => void> //Function
    type T4 = TypeName<string[]> //Object
    type T5 = TypeName<any>; //string | number | boolean | Function | Object
    
    //用js来书写上面的如下
    function example() {
        return condition1 ? value1
             : condition2 ? value2
             : condition3 ? value3
             : value4;
    }
    // 等价于
    function example() {
        if (condition1) { return value1; }
        else if (condition2) { return value2; }
        else if (condition3) { return value3; }
        else { return value4; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    如果传入的是联合类型会发生什么结果

    type TypeName<T> = 
        T extends string ? string:
        T extends number ? number:
        T extends boolean ? boolean:
        T extends undefined ? undefined:
        T extends Function ? Function:
        Object;
    type T10 = TypeName<string | (() => void)>;//string | Function
    type T11 = TypeName<string | string[] | undefined>//string | object | undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 为什么T10 和 T11类型返回的是联合类型呢,因为TypeName属于分布式条件类型,在条件类型中,如果被检查的是一个"裸"类型参数,就是没有被数组,元组,或者Promise等包装过,则该条件类型被称为分布式条件类型,对于分布式条件类型来说,当传入的被检查类型是联合类型的时候,在运算过程中就会被依次运算

    // 裸类型
    type Naked<T> = T extends boolean ? "Y" : "N";
    //分布式条件类型,判断每一个值是否都符合boolean
    //是就为每一个值返回对应的结果
    type T0 = Naked<number | boolean>;// "Y" | "N"
    
    //非裸类型
    //判断传入的T当中的每一个值是否都符合boolean,是就只返回一个"Y",否则只返回一个"N"
    type WrappedTuple<T> = [T] extends [boolean] ? "Y" : "N";
    
    //判断传入的T当中的每一个值为boolean类型的数组
    type WrappedArray<T> = T[] extends boolean[] ? "Y" : "N";
    type WrappedPromise<T> = Promise<T> extends Promise<boolean> ? "Y" : "N";
    
    type T1 = WrappedTuple<number | boolean>;// "N"
    type T2 = WrappedArray<number | boolean>;// "N"
    type T3 = WrappedPromise<number |boolean>;// "N;
    
    type T4 = WrappedTuple<true | false>;// "Y";
    type T5 = WrappedArray<true | false>; //"Y"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 由以上结果可知,如果条件类型中的类型参数 T 被包装过该条件类型就不属于分布式条件类型所以在运算过程中就不会被分解成多个分支
      • 说通俗点就是分布式条件运算返回值为联合类型,会对每一个值进行判断
      • 非分布式条件运算就是对整体每一个值进行判断,返回值为普通的类型

    Exclude内置工具类型执行流程

    • ts内置工具类型Exclude作用是传入T,U,将这两个相同的值消除,不同的值提取(利用了条件类型)
      • 返回值为never代表抛弃
    type Exclude<T,U> = T extends U ? never : T;
    
    //返回:c
    type T4 = Exclude<'a' | 'b' | 'c','a' | 'b'>;
    
    //返回:never
    type T5 = Exclude<'a' | 'b' ,'a' | 'b'>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Exclude演示

    ts中的infer

    • 现在有一个需求,获取数组当中的类型,和函数返回的类型
    type T0 = string[];
    type T1 = () => string;
    
    //需求,获取数组的类型和返回值的类型要怎么做?
    //做法如下,
    type UnpackedArray<T> = T extends (infer U)[] ? U : T;
    type T0Result = UnpackedArray<T0>; //string
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • infer是什么呢?

      • T extends (infer U)[] ? U : T:是条件类型的语法,而extends字句中的infer U 引入了一个新的类型变量U,用于存储被推断的类型,可以理解为后面这个U将用于存储类型
    • infer注意的点?

      • infer只能在条件类型extends字句中使用,同时infer声明的类型变量只能在条件类型的true分支中可用
      type Wrong1<T extends (infer U)[]> = T[0] // Error
      type Wrong2<T> = (infer U)[] extends T ? U : T // Error
      type Wrong3<T> = T extends (infer U)[] ? T : U // Error
      
      • 1
      • 2
      • 3
    • 那么infer到底要怎么用呢?

      • 一句话:要判断的是什么样子,infer就长什么样子
    • 我们再来看看怎么判断函数的返回类型

    //我们接着来下面这个的返回值类型
    type T1 = () => string;
    type T0 = string[];
    type UnpackedFunction<T> = T extends (...args:any[]) => (infer U) ? U : T;
    //返回 string
    type T1Types = UnpackedFunction<T1>
    
    //返回 string[]
    type Test = UnpackedFunction<T0>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当遇到函数重载的场景,TypeScript 将使用最后一个调用签名进行类型推断

    declare function foo(x:string):number;
    declare function foo(x:number):string;
    declare function foo(x:string | number):string | number;
    
    type UnpackedFn<T> = T extends (...args:any[]) => (infer U) ? U : T ;
    
    //返回:string | number;
    //代表返回类型
    type U2 = UnpackedFn<typeof foo>; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    利用条件链我们可以实现功能更加强大的 Unpacked 工具类型。

    • 还是那句话,使用infer,要判断的长什么样子,infer就长什么样子(括号包起来)
    type Unpacked<T> = 
        T extends (...args:any[]) => (infer U) ? U : 
        T extends (infer U)[] ? U :
        T extends Promise<(infer U)> ? U : T;
    type T0 = Unpacked<string>;  // string
    type T1 = Unpacked<string[]>;  // string
    type T2 = Unpacked<() => string>;  // string
    type T3 = Unpacked<Promise<string>>;  // string
    type T4 = Unpacked<Promise<string>[]>;  // Promise
    type T5 = Unpacked<Unpacked<Promise<string>[]>>;  // string
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    利用条件类型和 infer,我们还可以推断出对象类型中键的类型

    type User = {
        id: number;
        name: string;
    }
    
    type PropertyType<T>  = T extends {id: (infer U),name: (infer R) } ? [U,R] : T;
    type U3 = PropertyType<User> // [number, string]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    推断类型-二个类型
    • 在 PropertyType 工具类型中,我们通过 infer 声明了两个类型变量 U 和 R,分别表示对象类型中 id 和 name 属性的类型。若类型配,我们就会以元组的形式返回 id 和 name 属性的类型。那么现在问题来了,在 PropertyType 工具类型中,如果只声明一个类型变量 U,那结果又会是怎样呢?下面我们来验证一下:
    type PropertyType<T> =  T extends { id: infer U, name: infer U } ? U : T
    
    type U4 = PropertyType<User> // string | number
    
    • 1
    • 2
    • 3
    • 由以上代码可知,U4 类型返回的是 string 和 number 类型组合成的联合类型。为什么会返回这样的结果呢?这是因为在协变位置上,若同一个类型变量存在多个候选者,则最终的类型将被推断为联合类型
    推断类型-只有一个变量
    • 然而,在逆变位置上,若同一个类型变量存在多个候选者,则最终的类型将被推断为交叉类型。同样,我们来实际验证一下:
    export interface tempType {
        a:(x:string) => void,
        b:(x:number) => void,
    }
    type Bar<T> = T extends
        {
            a:(x:infer U) => void,
            b:(x:infer U) => void
        }
        ? U : never;
    // string 和 number 类型组合成的交叉类型
    // 即最终的类型是 never 类型
    type U5 = Bar<tempType>; //string & number 为never
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ts的常用工具库

    @部分转载CSDN织_网

    Partial
    • 源码
    type Partial<T> = {
        [P in keyof T]?: T[P]
    }
    
    • 1
    • 2
    • 3

    作用: Partial;把T当中的所有属性都变为可选

    详细:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为可选项

    interface Foo {
        name: string
        age: number
    }
    type Bar = Partial<Foo>
    // 相当于
    type Bar = {
        name?: string
        age?: number
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    Required
    • 源码
    type Required = {
    	[K in keyof T]-?:T[K]
    }
    
    • 1
    • 2
    • 3

    作用:Required:将T所有属性变为必填的

    详细:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选项

    interface Foo {
        name: string
        age?: number
    }
    type Bar = Required<Foo>
    // 相当于
    type Bar = {
        name: string
        age: number
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    Pick
    • 源码
    type Pick = {
    	[K in U]:T[K]
    }
    
    • 1
    • 2
    • 3

    作用:Pick(A,B);从A当中挑选B并返回

    详细:生成一个新类型,该类型拥有 T 中的 K 属性集 ; 新类型 相当于 T 与 K 的交集

    interface Foo {
        name: string;
        age?: number;
        gender: string;
    }
    type Bar = Pick<Foo, 'age' | 'gender'>
    // 相当于
    type Bar = {
        age?: number
        gender: string
    }
    
    const todo: Bar= {
       age?: 3,
       gender:};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    Exclude
    • 源码
      • 分布式条件类型来说,当传入的被检查类型是联合类型的时候,在运算过程中就会被依次运算
    type Exclude<T,U> = T extends U ? never : T;
    
    • 1

    作用:Exclude; 排除A当中的B

    详细: 如果 T 是 U 的子类型则返回 never 不是则返回 T(never可以理解为丢弃值不会返回)

    type A = number | string | boolean
    type B = number | boolean
    
    type Foo = Exclude<A, B>
    // 相当于
    type Foo = string
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    Extract
    • 源码
    type Extract<T,U> = T extends U ? T : never;
    
    • 1

    作用:Extract 从A中提取B

    详细: 如果 T 是 U 的子类型则返回 T 不是则返回 never (never可以理解为丢弃值不会返回)

    type A = number | string | boolean
    type B = number | boolean
    
    type Foo = Extract<A, B>
    // 相当于
    type Foo = number | boolean
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    Omit
    • 源码
    type Omit<T,K extends keyof any> = Pick<T,Exclude<T,K>>
    
    • 1

    作用:Exclude; 排除A当中的B

    详细: 如果 T 是 U 的子类型则返回 never 不是则返回 T(never可以理解为丢弃值不会返回)

    type Foo = {
    	name: string
    	age: number
    }
    
    type Bar = Omit<Foo, 'age'>
    // 相当于
    type Bar = {
    	name: string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    NonNullable
    • 源码
    type NonNullable<T> = T extends null | undefined ? never : T;
    
    • 1

    作用:NonNullable;从T中排除null 和 undefined

    详细: 从泛型 T 中排除掉 null 和 undefined

    type t = NonNullable<'name' | undefined | null>;
    //相当于
    // type t = "name"
    
    • 1
    • 2
    • 3
    Parameters
    • 源码
    type Parameters<T extends (...args:any) => any> 
    	= T extends (...args:infer P) => any ? P: never;
    
    • 1
    • 2

    作用:Parameters< (形参) => 返回值 > 以元组的形式返回形参

    详细: 以元组的方式获得函数的入参类型

    type t = Parameters<(name: string) => any>; // type t = [string]
    
    type t2 = Parameters<((name: string) => any)  | ((age: number) => any)>; // type t2 = [string] | [number]
    
    • 1
    • 2
    • 3
    ReturnType
    • 源码
    export type ReturnType<T extends (...args:any) => any> 
        = 
        T extends (...arg:any) => infer R ? R : any;
    
    • 1
    • 2
    • 3

    作用:ReturnType< (形参) => 返回值 >

    详细: 获得函数返回值的类型

    type t = ReturnType<(name: string) => string | number>
    // type t = string | number
    
    • 1
    • 2
    Uppercase
    • 源码
    type Uppercase<S extends string> = intrinsic
    
    • 1

    Uppercase将StringType转为大写,TS以内置关键字intrinsic来通过编译期来实现。

    type a = Uppercase<'abcDEF'>
    //等同于
    type a = 'ABCDEF';
    
    • 1
    • 2
    • 3
    Lowercase
    • 源码
    type Lowercase<S extends string> = intrinsic;
    
    • 1

    Lowercase将StringType转为小写,TS以内置关键字intrinsic来通过编译期来实现。

    type a = Lowercase<'abcDEF'>
    //等同于
    type a = 'abcdef';
    
    • 1
    • 2
    • 3
    Capitalize
    • 源码
    type Capitalize<S extends string> = intrinsic;
    
    • 1

    Capitalize将StringType首字母转为大写。

    type CapitalizeExample = Capitalize<"abc">;
    //等同于
    type CapitalizeExample = "Abc"
    
    • 1
    • 2
    • 3
    Uncapitalize
    • 源码
    type Uncapitalize<S extends string> = intrinsic;
    
    • 1

    Uncapitalize将StringType首字母转为小写

    type a = Uncapitalize<'AbcDEF'>
    //等同于
    type a = 'abcDEF';
    
    • 1
    • 2
    • 3
  • 相关阅读:
    Ubuntu 22.04上text-generation-webui service文件编写思路
    上海亚商投顾:沪指继续震荡向上 零售等消费股表现活跃
    检查网络端口是否正常
    kyuubi提交任务异常报错Unauthorized connection for super-user from IP
    STM32G0-BSP板级支持包
    Body Glove 与 Yeti Out 推出复古街头时尚系列
    个推解读Android13新特性,发布《Android13适配指南》
    系数( coefficients)、因数或因子( factors)
    2023年电工(初级)证考试题库及电工(初级)试题解析
    《Java》深浅拷贝解析(还不会区分深浅拷贝吗?快进来)
  • 原文地址:https://blog.csdn.net/u014582342/article/details/127953633