likes
comments
collection
share

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

作者站长头像
站长
· 阅读数 19

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

this 相关

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

ThisParameterType

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

function sing(this: { name: string }, age: number) {
  console.log(`是谁在唱歌? 是${this.name},今年${age}岁`)
}

type singType = typeof sing
type singThisType = ThisParameterType<singType>

获取的 singThisType 结果如下:

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

OmitThisParameter

OmitThisParameter 就是用于移除函数类型中的 this 参数类型,然后返回函数的类型:

type singOmitThisType = OmitThisParameter<singType>

sing 的参数去除掉 this 参数,只有一个 age,所以结果如下:

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

ThisType

ThisType 用于绑定上下文的 this 的类型。在下例中,我们想让 sing 中的 this 指向 singer.info,于是在第 17 行通过 call 去调用,绑定了 this,但 ts 并不知道第 14 行的 this 的类型为 IInfo,所以我们可以通过给 sing 传入 this 方式来进行类型注释:

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

// ...
const singer: ISinger & ThisType<IInfo> = {
  info: {
    name: 'Jay'
  },
  sing() {
    console.log(`是谁在唱歌? 是${this.name}`)
  }
}

映射类型相关

先来介绍下何谓映射类型

映射类型(Mapped Types)

假设现有 IPerson

interface IPerson {
  name: string
  age: number
}
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 的类型,结果如下:

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

如果原本 IPerson 的属性就是可选的,可以使用 -? 将新类型改为属性都是必须的:

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

除了可以添加 ?-?,也可以在 [p in keyof T] 前面添加 readonly 让新类型的属性都是只读的:

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

Partial、Required 与 Readonly

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

  • Partial<T> 传入一个泛型,将 T 的所有属性变为可选的;
  • Required<T> 传入一个泛型,将 T 的所有属性变为必须的;
  • Readonly<T> 传入一个泛型,将 T 的所有属性变为只读的。
type PersonPartialType = Partial<IPerson>
type PersonRequiredType = Required<IPerson>
type PersonReadonlyType = Readonly<IPerson>

Record

Record<K, T> 传入 2 个泛型,得到的对象类型中,属性(键)都是 K (联合类型)里的类型,值都是 T 类型:

type t1 = 'a' | 'b'
interface IPerson {
  name: string
  age: number
}

type RecordType = Record<t1, IPerson>

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

手写实现如下,为了保证传入的值是可以作为键的类型,所以 K 需要 extends keyof any,效果等同于 extends string | number | symbol

type MyRecord<K extends keyof any, T> = {
  [p in K]: T
}

比如在 vue router 中,RouteRecordNormalized 接口的属性 components,其类型就用到了 Record

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

Pick

从一个类型中挑选几个属性组成一个新类型:

interface IPerson {
  name: string
  age: number
  gender: string
}


type PickType = Pick<IPerson, 'name' | 'age'>

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

手写实现比较简单,只要保证传入的 K 的类型是 T 的属性即可:

type MyPick<T, K extends keyof T> = {
  [p in K]: T[p]
}

在一些框架的源码里,Pick 被使用的频率还是挺高的,比如 nuxt3 中对于接口 HeadSafe 的定义:

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

Omit

从一个类型中去掉几个属性,剩余的属性组成一个新类型:

interface IPerson {
  name: string
  age: number
  gender: string
}

type OmitType = Omit<IPerson, 'gender'>

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

手写实现如下,先让 p in keyof T 获取 T 的每个属性,然后 as p 获取到新的 p,对这个 p 做个条件判断,看看是否继承自 K,如果是就去掉,返回 never,如果不是则保留,返回 p 本身:

type MyOmit<T, K extends keyof T> = {
  [p in keyof T as p extends K ? never : p]: T[p]
}

在 nuxt3 的源码中,类型别名 HeadPluginOptions 的定义中,就用到了 Omit:

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

分发条件类型相关

所谓分发条件类型(Distributive Conditional Types),在使用泛型时,如果传入了一个联合类型,运用条件类型对泛型做判断时,联合类型的每个类型都会被测试是否满足条件。

Extract

Extract<T, U> 用于从一个联合类型中挑选几个类型组成新类型:

type gender = 'male' | 'female' | 'others'
type ExtractType = Extract<gender, 'male' | 'female'>

其实现如下,泛型 T 传入了一个联合类型,T extends U ? T : never 就会把 T 的每个类型('male''female''others')单独拿出来检测下是不是继承自 U,是则保留,返回 T ,不是就排除,返回 never

type MyExtract<T, U> = T extends U ? T : never

Exclude

Extract 功能相反,Exclude<T, U> 用来对联合类型的成员进行排除:

type gender = 'male' | 'female' | 'others'
type ExcludeType = Exclude<gender, 'others'>

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

其实现只需将之前实现的 MyExtract 的判断返回结果的去留做个顺序对调即可:

type MyExclude<T, U> = T extends U ? never : T

NonNullable

NonNullable<T> 用于去除联合类型中的 nullundefined

type gender = 'male' | 'female' | null | undefined
type NonNullableType = NonNullable<gender>

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

实现如下:

type MyNonNullable<T> = T extends null | undefined ? never : T

函数相关

接下来讲的 2 个内置工具 ReturnTypeParameters 都需要先准备一个函数 add,它的返回值类型为 number,作为通用的例子:

function add(a: number, b: number): number {
  return a + b
}

ReturnType

ReturnType<T> 用于获取函数的返回值类型。我们可以通过将函数 add 的类型传给 ReturnType,从而获取到 add 的返回值类型:

type addReturnType = ReturnType<typeof add>

可以看到 addReturnTypenumber

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

查看 ReturnType 的实现,源码如下:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

等号左边通过 extends 让传入 ReturnType 的类型 T 必须是个函数;

因为已经约束了 T 必为函数,所以等号右边的三元运算结果返回的必然是 R,这个 R 是由 infer 声明的(名称可以改成 Return 或别的什么),声明的同时还推导了 R 的类型为 number,最后返回出去的 R 也就成了 number

注意,仅条件类型extends 子句中才允许 infer 声明。

Parameters

Parameters<T> 用于获取函数的参数类型。我们可以通过将函数 add 的类型传给 Parameters,从而获取到 add 的参数类型:

type addParamType = Parameters<typeof add>

它的值将是一个元组类型:

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

查看 Parameters 的实现,源码如下:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

可以看到和 ReturnType 的实现很像,也是用到了 infer 声明了 P 并推导出类型,然后返回。

其它

Awaited

Awaited<T> 用于获取 Promise 中的结果类型。换句话说,T 应该(而非必须)为 Promise 类型,而 Awaited<T> 获取的值,就是传给这个 promise 的 resolve() 的值的类型。比如现在有个类型 APromise<string>

type A = Promise<string>

那么将 A 传给 Awaited

type resultType = Awaited<A>

得到的 resultType 就应该为 string

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现 Awaited 的实现比较复杂,我们可以手写一个比较简单的 MyAwaited,完成基本的功能:

type MyAwaited<T> = T extends Promise<infer V> ? V : never

TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现 TS 中的内置工具ts 提供了一些全局可用的工具类型来辅助进行常见的类型转换,本文就对它们做个总结与分享,并且会手写实现

转载自:https://juejin.cn/post/7374224361560342562
评论
请登录