TS 中的内置工具

ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现部分工具类型,或对它们在框架源码中的运用进行举例。

this 相关

先来介绍 3 个与 this 相关的:

ThisParameterType

用于提取函数的类型中 this 的类型。首先通过 typeof 获取函数类型,然后以泛型的写法传给 ThisParameterType 获取 singthis 的类型:

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
}

其内的属性都是必须的,如果我们想定义一个同样有着 nameage 属性的类型,只不过属性都是可选的,就可以通过映射类型实现,其写法融合了泛型索引签名的知识点:

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

上面使用联合类型实现的 MapPartialTypeMapRequiredTypeMapReadonlyType 其实就是 ts 提供的内置工具 PartialRequiredReadonly 的实现:

  • 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> 用于去除联合类型中的 nullundefined

typescript 复制代码
type gender = 'male' | 'female' | null | undefined
type NonNullableType = NonNullable<gender>

实现如下:

typescript 复制代码
type MyNonNullable<T> = T extends null | undefined ? never : T

相关推荐
y先森1 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy1 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189111 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿2 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡3 小时前
commitlint校验git提交信息
前端
Jacky(易小天)3 小时前
MongoDB比较查询操作符中英对照表及实例详解
数据库·mongodb·typescript·比较操作符
虾球xz4 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇4 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒4 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员4 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js