• TypeScript(四)泛型编程


    TypeScript

    软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性:
    比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作;
    但是对于参数的类型是否也可以参数化呢?

    什么是类型的参数化?
    我们来提一个需求:封装一个函数,传入一个参数,并且返回这个参数;
    如果我们是TypeScript的思维方式,要考虑这个参数和返回值的类型需要一致:

    function foo(arg: number):number {
    	return arg
    }
    
    • 1
    • 2
    • 3

    上面的代码虽然实现了,但是不适用于其他类型,比如string、boolean、Person等类型:
    我么可以使用any来解决这个问题

    function foo(arg: any):any {
    	return arg
    }
    
    • 1
    • 2
    • 3

    虽然any是可以的,但是定义为any的时候,我们其实已经丢失了类型信息:
    比如我们传入的是一个number,那么我们希望返回的可不是any类型,而是number类型;
    所以,我们需要在函数中可以捕获到参数的类型是number,并且同时使用它来作为返回值的类型;

    我们需要在这里使用一种特性的变量 - 类型变量(type variable),它作用于类型,而不是值:

    function foo<Type>(arg: Type): Type{
    	return arg
    }
    
    • 1
    • 2
    • 3

    这里我们可以使用两种方式来调用它:
    方式一:通过 <类型> 的方式将类型传递给函数;
    方式二:通过类型推导(type argument inference),自动推到出我们传入变量的类型:

    在这里会推导出它们是 字面量类型的,因为字面量类型对于我们的函数也是适用的

    foo<string>('abc')
    foo<number>(123)
    
    foo('bcd')
    foo(234)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    泛型的基本补充

    当然我们也可以传入多个类型

    function foo<T, E>(al: T, a2: E) {
    
    }
    
    • 1
    • 2
    • 3

    平时在开发中我们可能会看到一些常用的名称:
    T:Type的缩写,类型
    K、V:key和value的缩写,键值对
    E:Element的缩写,元素
    O:Object的缩写,对象

    泛型接口

    在定义接口的时候我们也可以使用泛型

    interface IFoo<T> {
    	initialValue: T,
    	valueList: T[],
    	handleValue: (value: T) => void
    }
    
    fonst foo: IFoo<number> = {
    	initialValue: 0,
    	valueList: [0, 2, 3],
    	handleValue: function(value: number) {
    		console.log(value)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    泛型类

    我们也可以编写一个泛型类:

    class Point<T, K> {
    	x: T;
    	y: K;
    	constructor(x: T, y: K) {
    		this.x = x;
    		this.y = y;
    	}
    }
    
    const p1 = new Point(10, 20);
    const p2 = new Point<string, string>('123', '234');
    
    console.log(p1.x);
    console.log(p2.x);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    泛型约束(Generic Constraints)

    有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:
    比如string和array都是有length的,或者某些对象也是会有length属性的;
    那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?

    interface ILength {
    	length: number;
    }
    //T相当于是一个变量 用于记录本次调用的类型 所以在整个函数的执行周期中 一直保留着参数类型
    function getInfo<T extends ILength>(args: T): T {
    	return args;
    }
    
    const info1 = getInfo('aaaa');
    const info2 = getInfo(['aaa', 'bbb', 'ccc']);
    const info3 = getInfo({ length: 100 });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里表示是传入的类型必须有这个属性,也可以有其他属性,但是必须至少有这个成员。

    在泛型约束中使用类型参数(Using Type Parameters in Generic Constraints)

    你可以声明一个类型参数,这个类型参数被其他类型参数约束;
    举个栗子:我们希望获取一个对象给定属性名的值
    我们需要确保我们不会获取 obj 上不存在的属性;
    所以我们在两个类型之间建立一个约束;

    //传入的key的类型 obj当中key的其中之一
    
    interface IPerson{
    	name: string;
    	age: number;
    }
    type IPersonKeys = keyof IPerson; //相当于 "name" | "age"  key的联合类型
    
    // function getObjectProperty(obj: O, key: K) {
    // 	return obj[key];
    // }
    function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
    	return obj[key];
    }
    
    const info = {
    	name: 'kobe',
    	age: 18,
    	height: 1.88,
    };
    
    const name = getObjectProperty(info, 'name');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    映射类型(Mapped Types)

    有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。
    大部分内置的工具都是通过映射类型来实现的;
    大多数类型体操的题目也是通过映射类型完成的;

    映射类型建立在索引签名的语法上:
    映射类型,就是使用了 PropertyKeys 联合类型的泛型;
    其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型;

    ts提供了映射类型: 函数
    映射类型不能使用interface定义

    type MapPerson<Type> = {
    	// 索引类型依次进行使用
    	[Property in keyof Type]: Type[Property];
    };
    
    interface IPerson {
    	name: string;
    	age: number;
    }
    type NewPerson = MapPerson<IPerson>;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    映射修饰符(Mapping Modifiers)

    在使用映射类型时,有两个额外的修饰符可能会用到:
    一个是 readonly,用于设置属性只读;
    一个是 ? ,用于设置属性可选;

    你可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。

    type MapIPerson<Type> = {
    	readonly [Property in keyof Type]?: Type[Property];
    };
    
    interface IPerson {
    	name: string;
    	age: number;
    	height: number;
    	address: string;
    }
    
    type IPersonOptional = MapIPerson<IPerson>;
    const p: IPersonOptional = {};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    type MapIPerson<Type> = {
    	-readonly [Property in keyof Type]-?: Type[Property];
    };
    
    interface IPerson {
    	name: string;
    	age?: number;
    	readonly height: number;
    	address: string;
    }
    
    type IPersonOptional = MapIPerson<IPerson>;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    内置工具与类型体操

    类型系统其实在很多语言里面都是有的,比如Java、Swift、C++等等,但是相对来说TypeScript的类型非常灵活:
    这是因为TypeScript的目的是为JavaScript添加一套类型校验系统,因为JavaScript本身的灵活性,也让TypeScript类型系统
    不得不增加更附加的功能以适配JavaScript的灵活性;
    所以TypeScript是一种可以支持类型编程的类型系统;
    这种类型编程系统为TypeScript增加了很大的灵活度,同时也增加了它的难度:
    如果你不仅仅在开发业务的时候为自己的JavaScript代码增加上类型约束,那么基本不需要太多的类型编程能力;
    但是如果你在开发一些框架、库,或者通用性的工具,为了考虑各种适配的情况,就需要使用类型编程;
    TypeScript本身为我们提供了类型工具,帮助我们辅助进行类型转换(前面有用过关于this的类型工具)。

    类型体操的题
    类型体操的一些题解

    条件类型(Conditional Types)

    很多时候,日常开发中我们需要基于输入的值来决定输出的值,同样我们也需要基于输入的值的类型来决定输出的值的类型。
    条件类型(Conditional types)就是用来帮助我们描述输入类型和输出类型之间的关系。
    条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ):
    SomeType extends OtherType ? TrueType : FalseType;

    function sum<T extends number | string>(num1: T, num2: T): T extends number ? number : string;
    function sum(num1, num2) {
    	return num1 + num2;
    }
    const res = sum(20, 30);
    const res2 = sum('abc', 'cba');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在条件类型中推断(inter)

    在条件类型中推断(Inferring Within Conditional Types)
    条件类型提供了 infer 关键词,可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果;
    比如我们现在有一个数组类型,想要获取到一个函数的参数类型和返回值类型:

    type CalcFnType = (num1: number, num2: number) => number;
    
    function foo() {
    	return 'abc';
    }
    //同届类型体操的题目: MyReturnType
    //推断返回值类型
    type MyReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R
    	? R
    	: false;
    
    //推断参数类型
    type MyParameterType<T extends (...args: any[]) => any> = T extends (...args: infer A) => any
    	? A
    	: never;
    
    //获取一个函数的返回值类型
    type CalcReturnType = MyReturnType<CalcFnType>;
    type FooReturnType = MyReturnType<typeof foo>;
    type FooReturnType1 = ReturnType<CalcFnType>;
    type CalcParameterType = MyParameterType<CalcFnType>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    分发条件类型(Distributive Conditional Types)

    在泛型中使用条件类型的时候 如果传入一个联合类型 就会变成分发的

    // type toArray = T[]; //(string | number)[]
    type toArray<T> = T extends any ? T[] : never; //number[] | string[]
    
    type NumArray = toArray<number>;
    
    //number[] | string[] 而不是(number | string)[]
    type NumAndStrArray = toArray<number | string>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果我们在 ToArray 传入一个联合类型,这个条件类型会被应用到联合类型的每个成员:
    当传入string | number时,会遍历联合类型中的每一个成员;
    相当于ToArray | ToArray;
    所以最后的结果是:string[] | number[];

    Partial

    用于构造一个Type下面的所有属性都设置为可选的类型

    interface IPerson {
    	name: string;
    	age: number;
    	slogan?: string;
    }
    type IPersonOption2 = Partial<IPerson>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    实现myPartial

    type MyPartial<T> = {
    	[p in keyof T]?: T[p];
    };
    
    //IKun类型都是可选的
    type IPersonOption1 = MyPartial<IPerson>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Required

    用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型跟 Partial 相反。

    interface IPerson {
    	name: string;
    	age: number;
    	slogan?: string;
    }
    type IKunOption1 = Required<IPerson>;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    实现MyRequired

    type MyRequired<T> = {
    	[p in keyof T]-?: T[p];
    };
    type IKunOption2 = MyRequired<IPerson>;
    
    • 1
    • 2
    • 3
    • 4

    Readonly

    用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值。

    interface IPerson {
    	name: string;
    	age: number;
    	slogan?: string;
    }
    type MyReadonly<T> = {
    	readonly [p in keyof T]: T[p];
    };
    type IPersonOption1 = Readonly<IPerson>;;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    实现MyReadonly

    type MyReadonly<T> = {
    	readonly [p in keyof T]: T[p];
    };
    type IPersonOption2 = MyReadonly<IPerson>;
    
    • 1
    • 2
    • 3
    • 4

    Record

    用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型。

    interface IPerson {
    	name: string;
    	age: number;
    	slogan?: string;
    }
    
    type t1 = '上海' | '北京' | '洛杉矶';
    type IPersons= Record<t1, IPerson>;
    
    const ipersons:IPersons = {
    	上海: {
    		name: 'xxx',
    		age: 10,
    	},
    	北京: {
    		name: 'yyy',
    		age: 5,
    	},
    	洛杉矶: {
    		name: 'zzz',
    		age: 3,
    	},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    实现MyRecord

    //确保keys一定是可以作为key的联合类型 Keys extends keyof any
    type keys = keyof IPerson; // name | age | slogan
    type Res = keyof any; // =>number | string | symbol
    
    type MyRecord<Keys extends keyof any, T> = {
    	[P in Keys]: T;
    };
    
    type t1 = '上海' | '北京' | '洛杉矶';
    type IPersons = MyRecord<t1, IPerson>;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Pick

    用于构造一个类型,它是从Type类型里面挑了一些属性Keys

    interface IPerson {
    	name: string;
    	age: number;
    	slogan?: string;
    }
    
    type IPerson1 = Pick<IPerson, 'name' | 'slogan'>;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    实现MyPick

    type MyPick<T, K extends keyof T> = {
    	[P in K]: T[P];
    };
    
    type IPerson2 = MyPick<IPerson, 'name' | 'slogan'>;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Omit

    用于构造一个类型,它是从Type类型里面过滤了一些属性Keys

    interface IPerson {
    	name: string;
    	age: number;
    	slogan?: string;
    }
    type IPerson1 = Omit<IPerson, 'name' | 'slogan'>;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    实现MyOmit

    //类型体操
    type MyOmit<T, K extends keyof T> = {
    	[P in keyof T as P extends K ? never : P]: T[P];
    };
    type IPerson2 = MyOmit<IPerson, 'name' | 'slogan'>;
    
    ```
    
    ## Exclude
    用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型。
    ```typescript
    type IPerson = '唱' | '跳' | 'rap';
    type IPerson1 = Exclude<IPerson, 'rap'>;
    ```
    
    实现MyExclude
    ```typescript
    type MyExclude<T, E> = T extends E ? never : T;
    type IPerson2 = MyExclude<IPerson, 'rap'>;
    ```
    有了MyExclude 我们可以使用它来实现MyOmit
    ```typescript
    type MyOmit<T, K> = Pick<T, MyExclude<keyof T, K>>
    ```
    
    ## Extract
    用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型。
    
    ```typescript
    type IPerson = '唱' | '跳' | 'rap';
    type IPerson1 = Extract<IPerson, 'rap'>;
    
    ```
    实现MyExtract
    ```typescript
    type MyExtract<T, E> = T extends E ? T : never;
    type IPerson2 = MyExtract<IPerson, 'rap'>;
    ```
    
    ## NonNullable
    用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型。
    
    ```typescript
    type IPerson = '唱' | '跳' | 'rap' | null | undefined;
    type IPerson1 = NonNullable<IPerson>;
    ```
    
    实现MyNonNullable
    ```typescript
    type MyNonNullable<T> = T extends null | undefined ? never : T;
    type IPerson2 = MyNonNullable<IPerson>;
    ```
    ## InstanceType
    用于构造一个由所有Type的构造函数的实例类型组成的类型。
    
    ```typescript
    class Person {}
    class Dog {}
    const p1: Person = new Person();
    
    //type Person 构造函数具体的类型
    //InstanceType 构造函数创建出来的实例对象的类型
    type MyPerson = InstanceType<typeof Person>;
    const p2: MyPerson = new Person();
    ```
    此时p1和p2都是Person类型 可能会觉得instanceType没有用
    但是我们可以看下面的情况
    ```typescript
    function factory<T extends new (...args: any[]) => any>(ctor: T): T {
    	return new ctor();
    }
    
    const p3 = factory(Person);
    const d = factory(Dog);
    ```
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/b2749c9547e14104850da1211e223fab.png)
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/4a074c44238d4e2983457089fb93dc41.png)
    此时返回的类型不是我们期望的
    ```typescript
    
    function factory<T extends new (...args: any[]) => any>(ctor: T): InstanceType<T> {
     	return new ctor();
    }
    const p3 = factory(Person);
    const d = factory(Dog);
    ```
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/1b6eb5b7f31b440c8b551c47500bb1b7.png)
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/2acb0e63e8064114ab807a1e6f43b37e.png)
    
    此时就正确了
    
    
    实现MyInstanceType
    ```typescript
    type MyInstanceType<T extends new (...args: any[]) => any> = T extends new (
    	...args: any[]
    ) => infer R
    	? R
    	: never;
    ```
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
  • 相关阅读:
    SAP S4客户与供应商如何管理 事务代码 BP
    2558. 从数量最多的堆取走礼物
    基于SpringBoot的校园交友网站
    LoRaWan模块应用于智慧城市景观灯
    即时通讯技术文集(第7期):长连接网关、P2P等 [共10篇]
    芒果叶病害数据集(用于图像分类,每类500张照片)
    主流前沿的开源监控和报警系统Prometheus+Grafana入门之旅-中
    【uniapp】HBuilderx中uniapp项目运行到微信小程序报错Error: Fail to open IDE
    UUCTF(公共赛道)
    阿里SQL又爆神作数据生态:MySQL复制技术与生产实践笔记
  • 原文地址:https://blog.csdn.net/weixin_65402230/article/details/128007563