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
