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]
相关推荐
greenbbLV4 分钟前
中小公司积分商城选型:SaaS与私有化方案对比解析
大数据·前端·小程序
NutShell Wang1 小时前
Vite 8.1 深度拆解:Rolldown 统一打包器如何终结前端构建的「双引擎时代」
前端·rust·开源·vite·开发者工具·vibe coding
朱容zr3331331 小时前
请解释“回表”的概念。
java·前端·数据库
leslie1182 小时前
webpack笔记
前端·webpack
宁风NF2 小时前
JavaScript:内存、垃圾回收、性能优化
开发语言·前端·javascript·学习·性能优化·es6
勾勾圈圈蛋蛋2 小时前
DOM、BOM、DOM操作详解 + 结合Vue响应式关联梳理
前端
打妖妖灵滴哪吒2 小时前
IOS模态窗口的作用与设计原则
前端·ui
TunerT_TQ2 小时前
GitHub深度工程评测:Cline 源码级工程评测|65k Star 开源AI编程助手的工程全景
typescript·开源·github·vs code·ai编程助手·开源供应链安全·静态源码审计
午安~婉3 小时前
挑战二:博客预览卡片
前端·javascript·html
环境栈笔记3 小时前
指纹浏览器安全评测方法:核对环境、数据与权限后再选型
前端·人工智能·后端·自动化