泛型

泛型是可以在保证类型安全的前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。日常我们创建的函数,传入什么数据就返回什么数据本身,即参数与返回值的类型相同,如下:

1
2
3
function add(value: number): number{
return value
}

而为了能够让函数接收任何类型,我们以前可以将参数类型修改为any。但这样就失去了TS的类型保护,类型不安全,泛型在保证类型安全的同时(不丢失类型信息),可以让函数与多种不同的类型一起工作,灵活可复用。

泛型函数使用

创建泛型函数:我们在创建函数的同时在函数名称后面添加 <> (尖括号),尖括号中添加类型变量,如下:

当然创建泛型函数的类型变量可以是任意合法变量名称,不仅仅是下面例子中的type。

1
2
3
4
5
// 类型变量 type 是一种特殊类型的变量,它只处理类型不处理值。
// 该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体类型由用户调用函数时指定)
function add<type>(value: type): type{
return value
}

调用泛型函数

在调用函数的同时在函数后面声明自己要传入参数的类型,用 <> 进行包裹。

1
2
3
4
5
6
7
8
// 创建泛型函数
function add<type>(value: type): type{
return value
}
// 调用泛型函数
const num = add<number>(10) // 以 number 类型调用函数
const str = add<string>('s') // 以 string 类型调用函数
const ret = add<boolean>(true) // 以 boolean 类型调用函数

当然在调用泛型函数时,可以省略<类型>来简化泛型函数的调用。因为TS内部会采用一种叫做类型参数推断的机制,来根据传入的参数自动推断出类型变量的类型。使用这种简化的方式调用泛型函数,使代码更短更便于阅读。

注意:当编译器无法推断类型或者推断的类型不准确时,就需要显式地传入类型参数。

泛型约束

默认情况下,泛型函数的类型变量可以代表多个类型,这就导致无法访问任何属性。比如当我们需要获取字符串长度时使用泛型就会报错,因为泛型代表任意类型无法保证一定存在某个属性,此时就需要为泛型 添加约束缩窄类型取值范围

添加泛型约束收缩类型主要有以下两种方式:

指定更具体的类型:可以根据自身需要将泛型函数的类型修改来获取属性,如下:

1
2
3
4
5
// 创建泛型函数
function add<type>(value: type[]): type{
console.log(value.length)
return value
}

添加约束:可以创建描述约束的接口来提供自己的属性,通过 extends 关键字为类型变量添加约束

1
2
3
4
5
6
7
8
9
10
interface MyLength {
length:number
}// 创建泛型函数
function add<type extends MyLength>(value: type): type{
console.log(value.length)
return value
}
add([1,2])
add('123')
add({name:'张三',length:12,age:13})

多泛型使用

泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如:第二个类型变量受第一个类型变量约束)。案例如下:

1
2
3
4
5
6
7
// 创建泛型函数
// keyof关键字接收一个对象类型,生成其键名称(或字符串或者数字)的联合类型
function add<type,key extends keyof type>(obj: type,value: key) {
return obj[value]
}
add(18,"toString")
add({name:'张三',age:18},"name")

泛型接口

接口的名称后面添加 <类型变量> ,那么这个接口就变成了泛型接口。接口也可以搭配泛型来使用,以增加其灵活性,增强其复用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
interface person<type> {
id: (value:type) => type
ids: ()=> type[]
}
// 使用泛型接口时一定要显式指定具体类型,否则报错
let p: person<number> ={
id(value){
return value
},
ids() {
return [1,2,3]
},
}

泛型类

class也可以配合泛型来使用。

创建泛型类

类似于泛型接口,在class名称后面添加<类型变量>,这个类就变成了泛型类。

1
2
3
4
5
6
7
8
9
class Animals<type> {
defaultValue: type
say: (x: number,y :number) => type
constructor(value: type){
this.defaultValue = value
}
}
// 当我们提供 constructor 并且里面已经提供类型了,这个时候我们new实例的时候,类型可以省略。// const num = new Animals<number>(10)
const num = new Animals(10)

泛型工具类型

TS内置了一些常用的工具类型,来简化TS中的一些常见操作。它们都是基于泛型实现的,并且都是内置的,可以直接在代码中使用。这些工具类很多,主要学习以下几个:

**Partial**:用来构造一个类型,将type的所有属性设置为可选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface People {
name: string
age: number
hobby: number[]
}
// 使的接口属性变为可选属性
type commenPeople = Partial<People>
// 调用接口,属性一定必须
let p1: People = {
name:'张三',
age:18,
hobby:[1,2]
}
// 使用 Partial 使的接口属性变为可选
let p2: commenPeople = {
name:''
}

**Readonly**:用来构造一个类型,将type的所有属性都设置为 readonly (只读)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface People {
name: string
age: number
hobby: number[]
}
// 使的接口属性变为可选属性
type ReadonlyPeople = Readonly<People>
// 调用接口,属性一定必须
let p1: ReadonlyPeople = {
name:'张三',
age:18,
hobby:[1,2]
}
// 无法分配到 "name" ,因为它是只读属性。
// p1.name = '李四'

Pick<type,keys>:从type中选择一组属性来构造新类型。Pick工具类型有两个类型变量:第一个表示选择谁的属性;第二个表示选择哪几个属性。注意:第二个类型变量传入的属性只能是第一个类型变量中存在的属性。

1
2
3
4
5
6
7
8
9
10
11
12
interface People {
name: string
age: number
hobby: number[]}
// 使的接口属性变为可选择属性
type RickPeople = Pick<People,'name'|'hobby'>
// 调用接口,属性一定必须
let p1: RickPeople = {name:'张三',
// 对象文字可以只指定已知属性,并且“age”不在类型“RickPeople”中。
// age:18,
hobby:[1,2]
}

**Record<keys,type>**:构造一个对象类型,属性键位keys,属性值为type。

1
2
3
4
5
6
type RecordObj = Record<'a'|'b'|'c',string[]>
let obj: RecordObj = {
a:['1'],
b:['1'],
c:['1']
}