四、TypeScript泛型编程
Lyk 2022/9/5 TypeScript泛型泛型类型参数化泛型接口泛型类泛型约束映射类型TS内置工具类型体操
# 1、认识泛型
- 软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性:
- 比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作;
- 但是对于参数的类型是否也可以参数化呢?
- 什么是类型的参数化?
- 我们来提一个需求:封装一个函数,传入一个参数,并且返回这个参数;
- 如果我们是TypeScript的思维方式,要考虑这个参数和返回值的类型需要一致:
function foo(arg: number) {
return arg
}
const num = foo(2) //num的类型:number
const str = foo('james')// ts报错提示:类型“string”的参数不能赋给类型“number”的参数。
1
2
3
4
5
6
2
3
4
5
6
- 上面的代码虽然实现了,但是不适用于其他类型,比如string、boolean、Person等类型:
- 下面虽然适用于其他类型,但是他们最后拿到的却是any类型
function foo(arg: any) {
return arg
}
const num = foo(2) // num的类型:any
const str = foo('james')// str的类型:any
1
2
3
4
5
6
2
3
4
5
6
# 2、泛型实现类型参数化
- 虽然any是可以的,但是定义为any的时候,我们其实已经丢失了类型信息:
- 比如我们传入的是一个number,那么我们希望返回的可不是any类型,而是number类型;
- 所以,我们需要在函数中可以捕获到参数的类型是number,并且同时使用它来作为返回值的类型;
- 我们需要在这里使用一种特性的变量 - 类型变量(type variable),它作用于类型,而不是值:
function foo<Type>(arg: Type):Type {
return arg
}
const num = foo(2) // num的类型:字面量类型:2
let num1 = foo(2) // num1的类型:number
const str = foo('james')// str的类型:字面量类型:'james'
let str1 = foo('james')// str1的类型:string
1
2
3
4
5
6
7
2
3
4
5
6
7
- 这里我们可以使用两种方式来调用它:
- 方式一:通过
<类型>
的方式将类型传递给函数; - 方式二:通过类型推导(type argument inference),自动推到出我们传入变量的类型:
- 在这里会推导出它们是 字面量类型的,因为字面量类型对于我们的函数也是适用的
- 方式一:通过
function foo<Type>(arg: Type):Type {
return arg
}
//方式一 泛型类型参数化
const num = foo<number>(123)
const str = foo<string>('rose')
//方式二
const num1 = foo(123) //字面量类型:123
const str1 = foo('rose')//字母量类型:'rose'
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 3、泛型的基本补充
- 当然我们也可以传入多个类型:
function foo<T, E>(a1: T, a2: E) {
console.log(typeof a1,typeof a2)
}
foo(1,'james')//number string
foo<string,boolean>('james',false)//string boolean
1
2
3
4
5
6
2
3
4
5
6
- 平时在开发中我们可能会看到一些常用的名称:
- T:Type的缩写,类型
- K、V:key和value的缩写,键值对
- E:Element的缩写,元素
- O:Object的缩写,对象
# 4、泛型接口
- 在定义接口的时候我们也可以使用泛型:
// interface IFoo<T> {
// initialValue: T
// valueList: T[]
// handleValue: (value: T) => void
// }
interface IFoo<T = string> {// 当然也可以给泛型参数指定默认类型
initialValue: T
valueList: T[]
handleValue: (value: T) => void
}
const foo: IFoo<number> = {
initialValue:0,
valueList:[1,2,3],
handleValue(v: number) {
console.log(v)
}
}
foo.handleValue(33)//33
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5、泛型类
- 我们也可以编写一个泛型类:
class Point<T> {
constructor(public x: T, public y: T) {}
}
const p1 = new Point(10,20)
const p2 = new Point<number>(20,500)
const p3: Point<number> = new Point(1,2)
1
2
3
4
5
6
7
2
3
4
5
6
7
# 6、泛型约束(Generic Constraints)
- 有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:
- 比如string和array都是有length的,或者某些对象也是会有length属性的;
- 那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?
- 这里表示是传入的类型必须有这个属性,也可以有其他属性,但是必须至少有这个成员。
interface ILength {
length: number
}
function getLength<T extends ILength>(args: T) {
return args.length
}
console.log(getLength("james"))
console.log(getLength(['nba','cba']))
console.log(getLength({name:'kobe',length:3}))
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 7、泛型约束(Generic Constraints)
- 在泛型约束中使用类型参数(Using Type Parameters in Generic Constraints)
- 你可以声明一个类型参数,这个类型参数被其他类型参数约束;
- 举个栗子:我们希望获取一个对象给定属性名的值
- 我们需要确保我们不会获取 obj 上不存在的属性;
- 所以我们在两个类型之间建立一个约束;
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
const info = {
name: "kobe",
age: 45
}
console.log(getProperty(info, 'name'))
console.log(getProperty(info, 'name1')) //ts报错提示:类型“"name1"”的参数不能赋给类型“"name" | "age"”的参数。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 8、映射类型(Mapped Types)
- 有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。
- 大部分内置的工具都是通过映射类型来实现的;
- 大多数类型体操的题目也是通过映射类型完成的;
- 映射类型建立在索引签名的语法上:
- 映射类型,就是使用了 PropertyKeys 联合类型的泛型;
- 其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型;
interface IPerson {
name: string
age: number
}
type MapType<T> = {
[property in keyof T]: T[property]
}
type NewPerson = MapType<IPerson> // NewPerson类型为:type NewPerson = {name: string;age: number;}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 9、映射类型(Mapped Types)
- 在使用映射类型时,有两个额外的修饰符可能会用到:
- 一个是 readonly,用于设置属性只读;
- 一个是 ? ,用于设置属性可选;
- 你可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。
//1. 映射时,映射 修饰符 到映射类型 NewPerson
type MapType<Type> = {
readonly [property in keyof Type]?: Type[property]
}
interface p{name:string,height:number}
type NewPerson = MapType<p>
//NewPerson的类型:
// type NewPerson = {
// readonly name?: string | undefined;
// readonly height?: number | undefined;
// }
//2.映射时,删除修饰符 到映射类型 NewPerson1
type MapType1<Type> = {
-readonly [property in keyof Type]-?: Type[property]
}
interface p{name:string,height:number}
type NewPerson1 = MapType1<p>
//NewPerson1的类型:
// type NewPerson1 = { //映射后,修饰符readonly和可选属性修饰符被删除了
// name: string;
// height: number;
// }
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 10、内置工具和类型体操
- 类型系统其实在很多语言里面都是有的,比如Java、Swift、C++等等,但是相对来说TypeScript的类型非常灵活:
- 这是因为TypeScript的目的是为JavaScript添加一套类型校验系统,因为JavaScript本身的灵活性,也让TypeScript类型系统不得不增加更附加的功能以适配JavaScript的灵活性;
- 所以TypeScript是一种可以支持类型编程的类型系统;
- 这种类型编程系统为TypeScript增加了很大的灵活度,同时也增加了它的难度:
- 如果你不仅仅在开发业务的时候为自己的JavaScript代码增加上类型约束,那么基本不需要太多的类型编程能力;
- 但是如果你在开发一些框架、库,或者通用性的工具,为了考虑各种适配的情况,就需要使用类型编程;
- TypeScript本身为我们提供了类型工具,帮助我们辅助进行类型转换(前面有用过关于this的类型工具)。
- 很多开发者为了进一步增强自己的TypeScript编程能力,还会专门去做一些类型体操的题目:
- 我们会学习TypeScript的编程能力的语法,并且通过学习内置工具来练习一些类型体操的题目。
# 11、条件类型(Conditional Types)
- 很多时候,日常开发中我们需要基于输入的值来决定输出的值,同样我们也需要基于输入的值的类型来决定输出的值的类型。
- 条件类型(Conditional types)就是用来帮助我们描述输入类型和输出类型之间的关系。
- 条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ):
SomeType extends OtherType ? TrueType : FalseType
- 条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ):
function sum<T extends number | string>(arg1: T, arg2: T): T extends string ? string : number
function sum(arg1: any,arg2: any) {
return arg1 + arg2
}
const num1 = sum('kobe','age is 45') //num1是string类型
const num2 = sum(599,20) //num2是number类型 ---> 相当于这种写法;const num2 = sum<number>(599,20)
const num3 = sum(true,2) //ts报道提示:类型“boolean”的参数不能赋给类型“string | number”的参数。
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 12、在条件类型中推断(infer)
- 在条件类型中推断(Inferring Within Conditional Types)
- 条件类型提供了 infer 关键词,可以从正在比较的类型中推断类型,然后在 true 分支里引用该推断结果;
- 比如我们现在有一个数组类型,想要获取到一个函数的参数类型和返回值类型:
type CalcFnType = (num1: number, num2: number) => number
type YKReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never
type CalcReturnType = YKReturnType<CalcFnType>// CalcReturnType类型为:type CalcReturnType = number
type YKParameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never
type CalcParameterType = YKParameters<CalcFnType>// CalcParameterType 的类型为:type CalcParameterType = [num1: number, num2: number]
1
2
3
4
5
6
7
2
3
4
5
6
7
# 13、分发条件类型(Distributive Conditional Types)
- 当在泛型中使用条件类型的时候,如果传入一个联合类型,就会变成 分发的(distributive)
type toArray<T> = T extends any ? T[] : never
type newType = toArray<string | number> // newType的类型:type newType = string[] | number[]
1
2
2
- 如果我们在 ToArray 传入一个联合类型,这个条件类型会被应用到联合类型的每个成员:
- 当传入string | number时,会遍历联合类型中的每一个成员;
- 相当于ToArray | ToArray;
- 所以最后的结果是:string[] | number[];
# 14、Partial<Type>
- 用于构造一个Type下面的所有属性都设置为可选的类型
interface IPerson {
readonly name: string,
age: number,
height: number
}
//1.使用ts内置工具Partial类型
type newType = Partial<IPerson>
//newType的类型为:
// type newType = {
// readonly name?: string | undefined;
// age?: number | undefined;
// height?: number | undefined;
// }
//2.自定义自己的Partial类型
type MyPartial<T> = {
[K in keyof T]?: T[K]
}
type newType1 = MyPartial<IPerson>
//newType1的类型为:
// type newType1 = {
// readonly name?: string | undefined;
// age?: number | undefined;
// height?: number | undefined;
// }
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
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
# 15、Required<Type>
- 用于构造一个Type下面的所有属性全都设置为必填的类型,这个工具类型跟 Partial 相反。
interface IPerson {
readonly name: string,
age?: number,
height?: number
}
//1.使用ts内置工具Required类型
type newType = Required<IPerson>
//newType的类型:
// type newType = {
// readonly name: string;
// age: number;
// height: number;
// }
//2.自定义自己的Required类型
type MyRequired<T> = {
[K in keyof T]-?: T[K]
}
type newType1 = MyRequired<IPerson>
//newType1的类型:
// type newType1 = {
// readonly name: string;
// age: number;
// height: number;
// }
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
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
# 16、Readonly<Type>
- 用于构造一个Type下面的所有属性全都设置为只读的类型,意味着这个类型的所有的属性全都不可以重新赋值
type IPerson = {
name: string,
age?: number,
height: number
}
//1.使用ts内置工具Readonly类型
type newType = Readonly<IPerson>
//newType类型:
// type newType = {
// readonly name: string;
// readonly age?: number | undefined;
// readonly height: number;
// }
//2.自定义自己的Readonly类型
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
type newType1 = MyReadonly<IPerson>
//newType1类型:
// type newType1 = {
// readonly name: string;
// readonly age?: number | undefined;
// readonly height: number;
// }
//3.自己构造一个Type下面的所有属性 全都清除 只读的类型
type clearReadonly<T> = {
-readonly [K in keyof T]: T[K]
}
type newType2 = clearReadonly<IPerson>
//newType2类型:
// type newType2 = {
// name: string;
// age?: number | undefined;
// height: number;
// }
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
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
# 17、Record<Type>
- 用于构造一个对象类型,它所有的key(键)都是Keys类型,它所有的value(值)都是Type类型
interface IPerson {
name: string,
age: number
}
type CityType = '上海' | '北京'
//1.使用ts内置工具Record类型
const data: Record<CityType,IPerson> = {
"上海": {name:'kobe',age:45},
"北京": {name:'james',age:37}
}
//2.自定义自己的Record类型
type MyRecord<Keys extends string | number | symbol,Type> = {
[K in Keys]: Type
}
const data1: MyRecord<CityType,IPerson> = {
"上海": {name:'kobe',age:45},
"北京": {name:'james',age:37}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 18、Pick<Type,Keys>
- 用于构造一个类型,它是从Type类型里面挑了一些属性Keys
interface IPerson {
name: string,
readonly age: number,
height: number
}
//1.使用ts内置工具Pick类型
type newPerson = Pick<IPerson,"name" | "age">
//newPerson的类型:
// type newPerson = {
// name: string;
// readonly age: number;
// }
//2.自定义自己的Pick类型
type MyPick<Type,Keys extends keyof Type> = {
[P in Keys]: Type[P]
}
type newPerson1 = MyPick<IPerson,"name" | "age">
//newPerson1的类型:
// type newPerson1 = {
// name: string;
// readonly age: number;
// }
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
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
# 19、Omit<Type,Keys>
- 用于构造一个类型,它是从Type类型里面过滤了一些属性Keys
interface IPerson {
name: string,
readonly age: number,
height: number,
}
//1.使用ts内置工具Omit类型
type newPerson = Omit<IPerson,"name" | "age">
//newPerson的类型:
// type newPerson = {
// height: number;
// }
//2.自定义自己的Omit类型
type MyOmit<Type,Keys> = {
[P in keyof Type as P extends Keys ? never : P]: Type[P]
}
type newPerson1 = MyOmit<IPerson,"name" | "age">
//newPerson1的类型:
// type newPerson1 = {
// height: number;
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 20、Exclude<UnionType, ExcludedMembers>
- 用于构造一个类型,它是从UnionType联合类型里面排除了所有可以赋给ExcludedMembers的类型。
type PropertyTypes = "name" | "age" | "height"
//1.使用ts内置工具Exclude类型
type newTypes = Exclude<PropertyTypes,"name">//newTypes的类型:type newTypes = "age" | "height"
//2.自定义自己的Exclude类型
type MyExclude<Type,Union> = Type extends Union ? never : Type
type newTypes1 = MyExclude<PropertyTypes,"name">//newTypes1的类型:type newTypes = "age" | "height"
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- 有了HYExclude,我们可以使用它来实现HYOmit。
# 21、Extract<Type,Union>
- 用于构造一个类型,它是从Type类型里面提取了所有可以赋给Union的类型
type PropertyTypes = "name" | "age" | "height"
//1.使用ts内置工具Extract类型
type newTypes = Extract<PropertyTypes,"name" | "age" | "address">//newTypes的类型:type newTypes = "name" | "age"
//2.自定义自己的Extract类型
type MyExtract<Type,Union> = Type extends Union ? Type : never
type newTypes1 = MyExtract<PropertyTypes,"name" | "age" | "address">//newTypes1的类型:type newTypes1 = "name" | "age"
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 22、NonNullable<Type>
- 用于构造一个类型,这个类型从Type中排除了所有的null、undefined的类型
type PropertyTypes = "name" | "age" | "height" | null | undefined
//1.使用ts内置工具NonNullable类型
type newTypes = NonNullable<PropertyTypes>
//newTypes的类型:type newTypes = "name" | "age" | "height"
//2.自定义自己的NonNullable类型
type MyNonNullable<Type> = Type extends null | undefined ? never : Type
type newTypes1 = MyNonNullable<PropertyTypes>
//newTypes1的类型:type newTypes1 = "name" | "age" | "height"
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 23、ReturnType<FnType>
- 用于构造一个含有Type函数的返回值的类型。
function sum(num1: number, num2: number) {
return num1 + num2
}
function getInfo(info: {name: string, age: number}) {
return info.name + info.age
}
//1.使用ts内置工具ReturnType类型
type newTypes = ReturnType<typeof sum>
//newTypes的类型:type newTypes = number
//2.自定义自己的ReturnType类型
// 第一个extends是对传入的条件进行限制
// 第二个extends是为了进行条件获取类型
type MyReturnType<Type extends (...args: any) => any> = Type extends (...args: any) => infer R ? R : never
type newTypes1 = MyReturnType<typeof getInfo>//newTypes1的类型:type newTypes1 = string
type newTypes2 = MyReturnType<() => void> //newTypes2的类型:type newTypes2 = void
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 24、InstanceType<Type>
- 用于构造一个由所有Type的构造函数的实例类型组成的类型。
class Person {
constructor(public name: string, public age: number) {}
}
type T = typeof Person
const info2: T = {name:'rose',age:32} //ts报错提示:不能将类型“{ name: string; age: number; }”分配给类型“typeof Person”。对象文字可以只指定已知属性,并且“name”不在类型“typeof Person”中。
//1.使用ts内置工具InstanceType类型
type PersonType = InstanceType<typeof Person>//PersonType的类型:type PersonType = Person
//对于普通的定义来说似乎是没有区别的
const info: Person = {name:'kobe',age:45}
const info1: PersonType = {name:'james',age:37}
//2.自定义自己的InstanceType类型
type MyInstanceType<Type extends new(...args: any[]) => any> = Type extends new(...args: any[]) => infer R ? R :never
type PersonType1 = MyInstanceType<typeof Person>//PersonType1的类型:type PersonType1 = Person
//3.InstanceType类型的应用
// 但是如果我们想要做一个工厂函数,用于帮助我们创建某种类型的对象
// 这里的返回值不可以以写T,因为T的类型会是 typeof Person
// 这里我们就可以使用InstanceType<Type>,它可以帮助我们返回构造函数的返回值类型(构造函数创建出来的实例对象类型)
function factory<T extends new (...args: any[]) => any> (ctor: T): InstanceType<T> {
return new ctor()
}
const p1 = factory(Person)//p1的类型:const p1: Person
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
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