ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现部分工具类型,或对它们在框架源码中的运用进行举例。
this 相关
先来介绍 3 个与 this
相关的:
ThisParameterType
用于提取函数的类型中 this
的类型。首先通过 typeof
获取函数类型,然后以泛型的写法传给 ThisParameterType
获取 sing
中 this
的类型:
typescript
function sing(this: { name: string }, age: number) {
console.log(`是谁在唱歌? 是${this.name},今年${age}岁`)
}
type singType = typeof sing
type singThisType = ThisParameterType<singType>
获取的 singThisType
结果如下:
至于 sing
函数中第一个参数传 this
这种写法,可参见 《TS 中的函数》。
OmitThisParameter
OmitThisParameter
就是用于移除函数类型中的 this
参数类型,然后返回函数的类型:
typescript
type singOmitThisType = OmitThisParameter<singType>
sing
的参数去除掉 this
参数,只有一个 age
,所以结果如下:
ThisType
ThisType
用于绑定上下文的 this
的类型。在下例中,我们想让 sing
中的 this
指向 singer.info
,于是在第 17 行通过 call
去调用,绑定了 this
,但 ts 并不知道第 14 行的 this
的类型为 IInfo
,所以我们可以通过给 sing
传入 this
方式来进行类型注释:
typescript
interface IInfo {
name: string
}
interface ISinger {
info: IInfo
sing: () => void
}
const singer: ISinger = {
info: {
name: 'Jay'
},
sing(this: IInfo) {
console.log(`是谁在唱歌? 是${this.name}`)
}
}
singer.sing.call(singer.info)
但如果 singer
中有好几个方法都需要让 this
指向 info
呢?每个方法都传一次 this
显得麻烦。还有一种办法,就是使用 ThisType
,告诉 ts 在 singer
中,this
的类型为 IInfo
:
typescript
// ...
const singer: ISinger & ThisType<IInfo> = {
info: {
name: 'Jay'
},
sing() {
console.log(`是谁在唱歌? 是${this.name}`)
}
}
注意:这里使用了交叉类型 &
来表明 singer
的类型是ISinger
的同时,this
的类型为 IInfo
。
映射类型相关
先来介绍下何谓映射类型。
映射类型(Mapped Types)
假设现有 IPerson
:
typescript
interface IPerson {
name: string
age: number
}
其内的属性都是必须的,如果我们想定义一个同样有着 name
和 age
属性的类型,只不过属性都是可选的,就可以通过映射类型实现,其写法融合了泛型和索引签名的知识点:
typescript
type MapPartialType<T> = {
[p in keyof T]?: T[p]
}
type PersonPartialType = MapPartialType<IPerson>
keyof T
得到的是 T 的各个属性的键的联合类型,keyof IPerson
得到的相当于是 'name' | 'age'
,p in keyof T
也就是将联合类型中的每个类型(T
类型中的所有属性)都遍历一遍,然后各自赋值为 T[p]
,不加 ?
就是完全复制了一个 T
类型。
现在查看 PersonPartialType
的类型,结果如下:
如果原本 IPerson
的属性就是可选的,可以使用 -?
将新类型改为属性都是必须的:
除了可以添加 ?
或 -?
,也可以在 [p in keyof T]
前面添加 readonly
让新类型的属性都是只读的:
Partial、Required 与 Readonly
上面使用联合类型实现的 MapPartialType
、MapRequiredType
和 MapReadonlyType
其实就是 ts 提供的内置工具 Partial
、Required
与 Readonly
的实现:
Partial<T>
传入一个泛型,将T
的所有属性变为可选的;Required<T>
传入一个泛型,将T
的所有属性变为必须的;Readonly<T>
传入一个泛型,将T
的所有属性变为只读的。
typescript
type PersonPartialType = Partial<IPerson>
type PersonRequiredType = Required<IPerson>
type PersonReadonlyType = Readonly<IPerson>
Record
Record<K, T>
传入 2 个泛型,得到的对象类型中,属性(键)都是 K
(联合类型)里的类型,值都是 T
类型:
typescript
type t1 = 'a' | 'b'
interface IPerson {
name: string
age: number
}
type RecordType = Record<t1, IPerson>
手写实现如下,为了保证传入的值是可以作为键的类型,所以 K
需要 extends keyof any
,效果等同于 extends string | number | symbol
:
typescript
type MyRecord<K extends keyof any, T> = {
[p in K]: T
}
比如在 vue router 中,RouteRecordNormalized
接口的属性 components
,其类型就用到了 Record
:
Pick
从一个类型中挑选几个属性组成一个新类型:
typescript
interface IPerson {
name: string
age: number
gender: string
}
type PickType = Pick<IPerson, 'name' | 'age'>
手写实现比较简单,只要保证传入的 K
的类型是 T
的属性即可:
typescript
type MyPick<T, K extends keyof T> = {
[p in K]: T[p]
}
在一些框架的源码里,Pick
被使用的频率还是挺高的,比如 nuxt3 中对于接口 HeadSafe
的定义:
Omit
从一个类型中去掉几个属性,剩余的属性组成一个新类型:
typescript
interface IPerson {
name: string
age: number
gender: string
}
type OmitType = Omit<IPerson, 'gender'>
手写实现如下,先让 p in keyof T
获取 T
的每个属性,然后 as p
获取到新的 p
,对这个 p
做个条件判断,看看是否继承自 K
,如果是就去掉,返回 never
,如果不是则保留,返回 p
本身:
typescript
type MyOmit<T, K extends keyof T> = {
[p in keyof T as p extends K ? never : p]: T[p]
}
在 nuxt3 的源码中,类型别名 HeadPluginOptions
的定义中,就用到了 Omit:
分发条件类型相关
所谓分发条件类型(Distributive Conditional Types),在使用泛型时,如果传入了一个联合类型,运用条件类型对泛型做判断时,联合类型的每个类型都会被测试是否满足条件。
Extract
Extract<T, U>
用于从一个联合类型中挑选几个类型组成新类型:
typescript
type gender = 'male' | 'female' | 'others'
type ExtractType = Extract<gender, 'male' | 'female'>
其实现如下,泛型 T
传入了一个联合类型,T extends U ? T : never
就会把 T
的每个类型('male'
、'female'
和 'others'
)单独拿出来检测下是不是继承自 U
,是则保留,返回 T
,不是就排除,返回 never
:
typescript
type MyExtract<T, U> = T extends U ? T : never
Exclude
与 Extract
功能相反,Exclude<T, U>
用来对联合类型的成员进行排除:
typescript
type gender = 'male' | 'female' | 'others'
type ExcludeType = Exclude<gender, 'others'>
其实现只需将之前实现的 MyExtract
的判断返回结果的去留做个顺序对调即可:
typescript
type MyExclude<T, U> = T extends U ? never : T
NonNullable
NonNullable<T>
用于去除联合类型中的 null
或 undefined
:
typescript
type gender = 'male' | 'female' | null | undefined
type NonNullableType = NonNullable<gender>
实现如下:
typescript
type MyNonNullable<T> = T extends null | undefined ? never : T