ts常用工具泛型的使用与实现

147 阅读4分钟

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的时候更加灵活,不用去定义一些重复的接口了。