ts的出现弥补了js不具备类型断言的缺点,让我们前端能够写出更加规范的代码。而泛型在ts中有着了举足轻重的地位。接下来我将为大家介绍泛型在ts中的运用,以及实现一些常见的工具泛型。 在实现之前,先看几个关键字:
keyof
可以把一个接口的所有属性名组成一个联合类型
interface animalInfo{
name: string,
age?: number,
weight?: number,
color: string,
hobby?: Array<string>
}
// 相当于
type animalInfo = 'name' | 'age' | 'weight' | 'color' | 'hobby'
extends
1、用于接口的继承
interface A{
name: string
}
interface B{
age: number
}
interface C extends A,B{
hobby: Array<string>
}
const info: C = {
name: '小明',
age: 18,
hobby: ['游泳']
}
2、作为泛型约束(条件类型)
interface A{
name: string,
}
interface B{
name: string,
age: number
}
type C = A extends B ? A : B // 此时A extends B为false,即这是C 等于B
以上作为条件类型,A extends B为false的原因是A中没有类型为number的age属性,所以A就满足不了B的约束,因而为false。
工具泛型的使用
interface animalInfo{
name: string,
age?: number,
weight?: number,
color: string,
hobby?: Array<string>
}
我们定义了一个动物的信息的接口,但有些业务需求只关心name跟color,有些业务需求又只关心name和hobby,这是我们又得增加两个接口:
interface catInfo{
name: string,
color: ''
}
interface dogInfo{
name: string,
hobby: Array<string>
}
如果之后又有又有一些其它的需求,比如只关心name和weight或只关心weight和color等,这样我们又要去定义一些其它的接口,这样在一个项目中太多的重复接口类型就是一笔不小的开销了。那么怎么灵活处理这一类的问题呢?ts的工具泛型就很好的解决了。如下:
const catInfo: Pick<animalInfo, 'name' | 'color'> = {
name: '小明',
color: 'white'
}
const dog: Pick<animalInfo, 'name' | 'hobby'> = {
name: '小王',
hobby: ['跑步', '游泳']
}
这样就不用定义那么多的重复接口了,在简单使用了一下工具之后,接下来将封装一些常用的工具泛型。
封装
Partial
让一个接口的所有属性都变成可选
type myPartial<T> = {
[P in keyof T] ?: T[P]
}
const catInfo: myPartial<animalInfo> = {
name: '小明',
color: 'white'
}
Pick
业务场景:只需要实现一个复杂接口的某几个属性
type myPick<T, K extends keyof T> = {
[P in K] : T[P]
}
const catInfo: myPick<animalInfo, 'name' | 'color'> = {
name: '小明',
color: 'white'
}
Required
和Partial相反,Required让一个接口的每一个属性都变成必选的
type myRequired<T> = {
[P in keyof T] -?: T[P]
}
const catInfo: myRequired<animalInfo> = {
name: '小明',
color: 'white',
age: 18,
weight: 10,
hobby: ['游泳']
}
Readonly
把接口的所有属性都定义为只读的
type myReadonly<T> = {
readonly [P in keyof T] : T[P]
}
const catInfo: myReadonly<animalInfo> = {
name: '小明',
color: 'white',
}
catInfo.name = '小王' // 报错,该属性是只读的,不能复制
Record
把联合类型的key当作新接口的key
type myRecord<K extends keyof any, T> = {
[P in K] : T
}
type propsInfo = 'name' | 'color'
const propsValue: myRecord<propsInfo, string> = {
name: '小明',
color: 'white'
}
Exclude
从一个联合类型(T)排除掉属于另一个联合类型(U)的子集
type myExclude<T, U> = T extends U ? never : T
type name = myExclude<'小明' | '小杨', '小刚' | '小明'>
// 相当于
type name = '小杨'
Extract
跟Exclude相反,从一个联合类型(T)拿出属于另一个联合类型(U)的子集
type myExtract<T, U> = T extends U ? T : never
type name = myExtract<'小明' | '小杨', '小刚' | '小明'>
// 相当于
type name = '小明'
type myOmit<T, K extends keyof T> = myPick<T, myExclude<keyof T, K>>
const propsValue: myOmit<animalInfo, 'name' | 'age'> = {
name: '小明', // 报错,应为propsValue中没有name属性了,被删除了
color: 'white'
}
myExclude<keyof T, K>:从T中排除掉同时存在K中的属性,即从animalInfo中排除掉name和age属性 Pick<T, myExclude<keyof T, K>>: 实现T中的myExclude<keyof T, K>元素,即实现animalInfo中除了name和age之外的其它元素。
ReturnType
返回的是一个函数返回值的类型
type myReturnType<T> = T extends (...arg: any) => infer R ? R : any
function returnPrams(x: number, y: number): number{
return x + y
}
type prams = myReturnType<typeof returnPrams>
const num: prams = 3232
这里多了一个infer关键字,是一个只能在 extends出现的时候才能使用的关键字。在条件类型语句中, 我们可以用 infer 声明一个类型变量并且对它进行使用。而这里的infer R就是用来声明一个类型变量R承载传入函数的返回值类型,即用R存储函数返回值类型,方便之后只用。
MyParameters
返回的是函数的参数类型
type MyParameters<T> = T extends (...arg: infer R) => void ? R : any
function returnPrams(x: number, y: string): string{
return x + y
}
type prams = MyParameters<typeof returnPrams>
const num: prams = [123, '321']
总结:
以上是我们常用的几种工具泛型,他让我们在使用ts的时候更加灵活,不用去定义一些重复的接口了。