泛型 泛型是可以在保证类型安全 的前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、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'] }