TypeScript 类型体操之实现数组的 Concat
本期一共三道题目: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;
如果 T
为 Promise<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