typescript 类型Prepend和Tail处理数组,但扩展到函数?

mu0hgdu0  于 2023-05-08  发布在  TypeScript
关注(0)|答案(1)|浏览(153)

我对这种类型的工作原理相当困惑。它接受一个数组并扩展到一个函数?它不应该扩展到一个数组吗?因为它处理的是一个数组?我希望有人能解释一下为什么它可以扩展到一个函数,什么是U?

type Prepend<E, T extends any[]> =
       ((head: E, ...args: T) => any) extends ((...args: infer U) => any)
       ? U
       : T

第二种类型的行为也类似。它接受一个数组,但将其作为函数处理。

type Tail<T extends readonly any[]> = 
   ((...t:T) => any) extends ((_:any, ...tail: infer TT) => any) 
   ? TT 
   : []

示例:

type T1 = Tail<[1,2,3]>  //2 ,3
type T2 = Tail<["A","B","C"]> // "B" , "C"
type T3 = Tail<[1,"A","Hello"]> //"A", "Hello"

type P1 = Prepend<string, []> //[string]
type P2 = Prepend<number, [1,2]> //[number,1,2]
juzqafwq

juzqafwq1#

在引入可变元组类型之前,没有直接的方法来连接或拆分元组类型。今天你会写

type Prepend<E, T extends any[]> = [E, ...T];
type X = Prepend<1, [2, 3, 4]>;
// type X = [1, 2, 3, 4];

type Tail<T extends readonly any[]> = T extends readonly [any, ...infer TT] ? TT : [];
type Y = Tail<["a", "b", "c"]>
// type Y = ["b", "c"];

Prepend类型现在相当无用,因为它比直接使用可变元组要长。但是对于Tail,如果你理解了infer的条件类型推断,你仍然可以看到发生了什么。编译器正在将T类型与某个元组进行匹配,该元组的第一个元素是any(它几乎可以匹配任何东西),而元组的 rest 是某个infer红色类型TT,它的计算结果为TT
但在此之前,唯一可用的方法是在元组类型和函数参数之间进行转换。像[string, number]这样的元组和像(a: string, b: number) => any这样的函数之间有一个自然的Map,编译器支持它们之间的各种转换,如TypeScript 3.0发行说明中所述。
注意函数参数的名称(以及后面出现的标记元组的 labels)不会影响类型,因此在下面的(a: string) => any(x: string) => any是相同的。而(...args: [string]) => any(...rest: [string]) => any(...blfdsjksdksjk: [string]) => any是相同的。
无论如何,(a: string, b: number) => any类型的函数有一个[string, number]类型的参数列表。由于rest parameters允许将参数列表打包到单个参数位置,因此可以说(a: string, b: number) => any(...args: [string, number]) => any是同一类型。现在函数形参列表和实参之间的关系应该更加明显了。
事实上,所有这些都是同一类型的:

type F1 = (a: 1, b: 2, c: 3, d: 4) => any;
type F2 = (...args: [1, 2, 3, 4]) => any;
type F3 = (z: 1, ...args: [2, 3, 4]) => any;

看看F3,希望这是有意义的:

type X = ((a: 1, ...args: [2, 3, 4]) => any) extends
  ((fgh: any, ...blrgh: infer TT) => any) ? TT : [];
// type X = [2, 3, 4]

很容易匹配第一个参数(因为any匹配任何东西),然后rest参数的类型是[2, 3, 4]。但由于F3F2相同,因此您得到

type X = ((...args: [1, 2, 3, 4]) => any) extends
  ((a: any, ...args: infer TT) => any) ? TT : [];
// type X = [2, 3, 4]

就像这样,我们已经得到了[1, 2, 3, 4]元组的尾部,通过从元组类型转换为函数类型,操作函数类型,然后转换回元组。这种技术可以成为一种实用程序类型:

type Tail<T extends readonly any[]> = 
   ((...t:T) => any) extends ((_:any, ...tail: infer TT) => any) 
   ? TT : []

这就是你要问的
Prepend类似,您可以在另一个方向进行匹配,其中您获取(head: E, ...args: T) => any类型的函数并尝试根据(...args: infer U) => any推断它,这将导致U与我们现在称为[E, ...T]的相同。
Playground链接到代码

相关问题