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

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

189 Awaited

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

题目描述

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

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

例如:

ts 复制代码
type ExampleType = Promise<string>

type Result = MyAwaited<ExampleType> // string

题目解析

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

先复习下 infer 的用法:

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

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

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

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

ts 复制代码
type MyAwaited<T extends Promise<any>> = T extends Promise<infer P> ? P : never;

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

但是在这个 case 报错了:

ts 复制代码
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

ts 复制代码
type MyAwaited<T extends Promise<any>> = T extends Promise<infer P>
  ? P extends Promise<any>
    ? MyAwaited<P>
    : P
  : never;

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

此时发现还有个 case 报错:

ts 复制代码
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 类型

ts 复制代码
type Thenable<T> = {
  then: (onfulfilled: (arg: T) => unknown) => unknown;
}

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

ts 复制代码
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 或者 falseTF 可以是任意类型。

例如:

ts 复制代码
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

ts 复制代码
type If<C extends boolean, T, F> = C extends true
  ? T
  : C extends false
  ? F
  : never;

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

ts 复制代码
type If<C extends boolean, T, F> = C extends true ? T : F;

533 Concat

题目要求

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

例如:

ts 复制代码
type Result = Concat<[1], [2]> // expected to be [1, 2]

题目解析

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

ts 复制代码
type Concat<T extends any[], U extends any[]> = [...T, ...U]

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

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

ts 复制代码
type Tuple1 = [number, string];
type Tuple2 = [boolean, number];
type Result = [...Tuple1, ...Tuple2]; // Result 的类型为 [number, string, boolean, number]

扩展运算符还可以在元组操作时表示剩余元素,实际上在 TypeScript 类型体操之 First of Array 中就已经用到了,不过当时没有在意(=_=#

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

ts 复制代码
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]
相关推荐
云边小卖铺.4 分钟前
运行vue项目报错 errors and 0 warnings potentially fixable with the `--fix` option.
前端·javascript·vue.js
我是若尘6 分钟前
前端处理大量并发请求的实用技巧
前端
Lstmxx7 分钟前
Electron:使用数据流的形式加载本地视频
前端·electron·node.js
JunjunZ15 分钟前
unibest框架开发uniapp项目:兼容小程序问题
前端·vue.js
lyc23333317 分钟前
鸿蒙Next应用启动框架AppStartup:流程管理与性能优化🚀
前端
Data_Adventure17 分钟前
Vue 3 作用域插槽:原理剖析与高级应用
前端·vue.js
curdcv_po25 分钟前
报错 /bin/sh: .../scrcpy-server: cannot execute binary file
前端
小公主26 分钟前
用原生 JavaScript 写了一个电影搜索网站,体验拉满🔥
前端·javascript·css
代码小学僧26 分钟前
通俗易懂:给前端开发者的 Docker 入门指南
前端·docker·容器
Moment28 分钟前
为什么我在 NextJs 项目中使用 cookie 存储 token 而不是使用 localstorage
前端·javascript·react.js