四、TypeScript泛型编程

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
  • 上面的代码虽然实现了,但是不适用于其他类型,比如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、泛型实现类型参数化

  • 虽然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
  • 这里我们可以使用两种方式来调用它:
    • 方式一:通过<类型>的方式将类型传递给函数;
    • 方式二:通过类型推导(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

# 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
  • 平时在开发中我们可能会看到一些常用的名称:
    • 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

# 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

# 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

# 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

# 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

# 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

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

# 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

# 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
  • 如果我们在 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

# 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

# 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

# 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

# 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

# 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

# 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
  • 有了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

# 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

# 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

# 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
最后更新时间: 2022/11/01, 11:56:41
彩虹
周杰伦