likes
comments
collection
share

TypeScript 类型体操之实现数组的 Concat

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

本期一共三道题目:189 Awaited、268 If 和 533 Concat

189 Awaited

P.S.这道题是第一道自己写出来的题目,开心^_^

题目描述

如果我们有一个被包装的类型,比如 Promise,我们如何获取包装类型内部的类型?

例如我们有 Promise<ExampleType>,请你返回 ExampleType 类型。

例如:

type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string

题目解析

在之前已经学习过 infer 的用法,在这里一下就想到了,在匹配模式类型后提取其中的类型。

先复习下 infer 的用法:

在 TypeScript 中,infer 是一个用于类型推断的关键字。它通常与条件类型(Conditional Types)一起使用,用于从给定的类型中提取或推断出其他类型。infer 使用举例:

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

在上面的例子中,infer R 表示从函数类型 T 的返回值中推断出类型 R。如果 T 是一个函数类型,则返回 R,否则返回 never

有了强大的工具 infer 之后我们可以推断一个类型是否符合 Promise<xxx> 的形式,具体代码如下:

type MyAwaited<T extends Promise<any>> = T extends Promise<infer P> ? P : never;

如果 TPromise<infer P> 的形式,我们就取 P 的值返回,否则就返回 never

但是在这个 case 报错了:

type Z = Promise<Promise<string | number>>;
type Z1 = Promise<Promise<Promise<string | boolean>>>;
Expect<Equal<MyAwaited<Z>, string | number>>
Expect<Equal<MyAwaited<Z1>, string | boolean>>

这两个例子都是嵌套 Promise,解法很简单,既然在上面的返回类型 P 有可能仍然为 Promise 对象,我们就对其再使用解包,即嵌套的调用 MyAwaited

type MyAwaited<T extends Promise<any>> = T extends Promise<infer P>
  ? P extends Promise<any>
    ? MyAwaited<P>
    : P
  : never;

TS 的嵌套和函数的递归调用很像,也很容易理解。

此时发现还有个 case 报错:

type T = { then: (onfulfilled: (arg: number) => any) => any };
Expect<Equal<MyAwaited<T>, number>>

我们知道在 JavaScript 中 Promise 提供了 then 方法,而 Promise-like 用于描述具有类似 Promise 的行为和特征的对象或类型,它具有 then 方法,并且遵循 Promise/A+ 规范,我们也把这种对象叫 Thenable 对象。Promise 的实现一般都会兼容 Thenable 对象。

所以在这里题目也要求我们用我们兼容 Thenable 对象。

我们先定义 Thenable 类型

type Thenable<T> = {
  then: (onfulfilled: (arg: T) => unknown) => unknown;
}

然后我们在之前答案的基础上加上对 Thenable 的处理:

type MyAwaited<T extends Promise<any> | Thenable<any>> = T extends Promise<
  infer P
>
  ? P extends Promise<any>
    ? MyAwaited<P>
    : P
  : T extends Thenable<infer F>
  ? F
  : never;

268 If

题目描述

实现一个工具类型 If<C, T, F>,它接收一个条件类型 C ,判断为真时的返回类型 T ,以及判断为假时的返回类型 F

C 只能是 true 或者 false, T 和 F 可以是任意类型。

例如:

type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

题目解析

首先我们通过 extends 进行条件类型判断,如果为 true 返回 T,如果为 false 返回 F,否则返回 never

type If<C extends boolean, T, F> = C extends true
  ? T
  : C extends false
  ? F
  : never;

不过这题还可以再简单一些,既然 C 只能为 true 或者 false,我们就不需要再判断第二次了:

type If<C extends boolean, T, F> = C extends true ? T : F;

533 Concat

题目要求

在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。

例如:

type Result = Concat<[1], [2]> // expected to be [1, 2]

题目解析

苦思冥想,百思不得其解~~ 结果看到答案非常非常简单:

type Concat<T extends any[], U extends any[]> = [...T, ...U]

如果对于数据值使用 ... 我们肯定非常熟悉,扩展运算符嘛,但是对于类型也可以?

是的,在 TS 中,扩展语法可以对元组类型使用,比如使用扩展语法将两个元组类型拼接为一个新的元组类型:

type Tuple1 = [number, string];
type Tuple2 = [boolean, number];
type Result = [...Tuple1, ...Tuple2]; // Result 的类型为 [number, string, boolean, number]

比如我们想获取一个数组除了前两个元素后剩下的数组类型

type ExceptFirstTwo<T extends any[]> = T extends [
  infer _a,
  infer _b,
  ...infer Rest
]
  ? Rest
  : never;
type Arr = [number, boolean, string, object];
type ArrExceptFirstTwo = ExceptFirstTwo<Arr>; // [string, object]

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