typescript-infer

loading 2023年05月22日 56次浏览

同样也是做类型体操的时候遇到的,记录一下:

1. Awaited

现在有这些数据:

type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }

要求把其中的类型取出来,比如X里的string,Y里的等。

三个问题:

  • 最基本的,如何取出来?
  • 嵌套情况下如何递归
  • 像type T这种类promise对象如何处理?

一个个解决,首先第一点就要用到infer,infer网上查了解释五花八门,其实就可以把它理解为一个占位符,来看具体的处理:

type MyAwaited<T> = T extends Promise<infer R> ? R : T;

很好理解,判断T是否属于Promise<>这种结构,是的话返回里面的R,否则直接返回T。

再加一层递归解决问题二:

type MyAwaited<T> = T extends Promise<infer R> ? MyAwaited<R> : T;

最后引入PromiseLike对象解决问题三:

type MyAwaited<T> = T extends PromiseLike<infer R> ? MyAwaited<R> : T;

当然这样还是存在小问题,就是判断不了一开始直接就传入基本类型值的情况,比如:

// @ts-expect-error
type error = MyAwaited<number>

因此最终答案会稍微复杂点,不过这题主要是为了熟悉infer的使用:

type MyAwaited<T extends PromiseLike<any | PromiseLike<any>>> =
  T extends PromiseLike<infer V>
    ? V extends PromiseLike<any>
      ? MyAwaited<V>
      : V
    : never

2. Include

先上测试数据:

type cases = [
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
  Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
  Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
  Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
  Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
  ...
]

实现Array.includes()方法,注意这里infer是如何占位的:

type Includes<T extends any[], U> = 
  T extends [infer First , ...infer Rest]
    ? Equal<First , U> extends true 
      ? true
      : Includes<Rest , U> 
    : false

首先是判断T是不是一个数组,然后用First占位数组中的第一个元素,剩下的用Rest占位。不是数组返回false。
如果T是一个数组,判断第一个元素和待检测元素是否相等,相当说明include,返回true。
不相等则递归检查剩余的数组元素,如果一直找不到Rest数组会变成空数组:

[] extends [infer First , ...infer Rest] // false

最终也会返回false。

这题主要就是学会数组的占位。

3. Parameters

需要实现的效果如图,也就是取到函数的参数类型:

const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: { a: 'A' }): void => {}
const baz = (): void => {}

type cases = [
  Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
  Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
  Expect<Equal<MyParameters<typeof baz>, []>>,
]

思路也是通过infer去占位需要取到的东西:

type MyParameters<T extends (...args: any[]) => any> = 
	T extends (...args: infer S) => any ? S : never;