likes
comments
collection
share

TypeScript 类型体操之 First of Array

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

TypeScript 类型体操之 First of Array

题目要求

实现一个通用 First<T>,它接受一个数组 T 并返回它的第一个元素的类型。

例如:

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3

题目实现

找到数组 T 第一项的类型还是比较简单,只需要 T[0] 即可,但是问题就是,如果数组为空,数组第一项的类型就是 never,所以题目的难点变成了使用 TS 判断一个数组是否为空数组。

看了一遍评论区之后,找到了下面几种解决方案:

1、T['length'] extends 0

type First<T extends any[]> = T['length'] extends 0 ? never : T[0]

T extends any[] 表示类型约束,用来限制 T 为一个数组。

T['length'] extends 0 中的 extends 来表示条件类型,在 TS 中 A extends B ? C : D 的意思是“如果 A 可以赋值给 B,那么类型就是 C,否则就是 D”。

在此表达式中 “B” 为 0 也就是说如果条件成立,那么 T['length'] 只能为 0,到此我们就完成了数组是否为空的判断。

综上,我们可以实现了 First<T> 返回 T 的第一个元素的类型。

2、T extends []

type First<T extends any[]> = T extends [] ? never : T[0]

和第一种实现方式类型,不过使用了 T extends [] 来判断了 T 类型是否为空数组。

3、T[number] extends never

type First<T extends any[]> = T[number] extends never ? never : T[0];

T 是一个数组,T[number] 表示 T 的下标为 number 的元素类型,也就是 T 中所有可能的元素类型组成的联合类型。

T[number] extends never 表示如果 T 的所有元素可能的类型是 never,也就是说 T 是一个空数组,则返回 never 类型;否则,返回 T 的第一个元素的类型。

4、'0' extends keyof T

type First<T extends any[]> = '0' extends keyof T ? T[0] : never;

我们可以通过 keyof T 来获取类型 T 所有键的类型组合,那么如果 '0'T 的键就表示 T 是一个非空数组。

如果 T 是一个非空数组, 返回 T[0],否则,返回 never 类型。

5、infer

在 TS 中,我们通过 extendsinfer 可以实现一个类似于模式匹配的效果。前面说过 A extends B ? C : D 的意思是“如果 A 可以赋值给 B,那么类型就是 C,否则就是 D”。而在 B 中我们把某个类型变量使用 infer T 来表示,并可以获取它。

举个例子:

// 我们使用 (...args: infer P) => any 表示了一个函数声明,其中使用 infer P 来表示函数的类型参数
// 如果传入的参数能够赋值给函数声明,就可以通过 P 来获取参数类型了
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

// 我们使用 T extends (...args: any) => infer R 表示了一个函数声明,其中使用 infer R 来表示函数的返回参数
// 如果传入的参数能够赋值给函数声明,就可以通过 R 来获取返回类型了
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

感觉有点类型于正则匹配,/<([^>]*)>/.exec('<html>') 我们使用 () 包裹要获取的内容,当整个正则和字符串匹配的时候,我们就能获取真正对应的字符串了。

现在我们可以实现题目

type First<T extends any[]> = T extends [infer A, ...infer _rest] ? A : never

T extends [infer A, ...infer _rest] ? A : never 中,如果 T 是一个至少有一个元素的数组,那么 A 就会推断为 T 的第一个元素的类型,然后返回 A;否则,返回 never 类型。

对 TypeScript 的理解

做完这道题突然有一个很大的收获,TypeScript是针对类型的编程,而不是针对数据的编程。

在做这道题的时候一直纠结一个问题

type First<T extends any[]> = T extends [infer A, ...infer rest] ? A : never

const arr = [1, 2, 3];
type ArrType = typeof arr
type ArrFirstType = First<ArrType>; // never

为什么在这种情况,arr 明明不是空数组,却会返回 never。但是后面突然明白了,当我通过 typeof arr 获取类型时,我们获取的是 number[],和 arr 具体的值并无关系。

TS 是在静态编译时对类型的限制,和运行时的具体数据并无关系。

我们对类型 [number, string] 获取第一个类型肯定为 number,对类型 [1, '2'] 获取第一个类型为 1,而对 [] 获取第一个类型则为 never

但是对于 number[] 来说,它并不一定有第一个元素,所以我们也没办法获取第一个元素的类型。所以个人认为这是一个未定义的结果。而对于测试样例,我们也能看到测试的类型都是具体的类型。

type cases = [
  Expect<Equal<First<[3, 2, 1]>, 3>>,
  Expect<Equal<First<[() => 123, { a: string }]>, () => 123>>,
  Expect<Equal<First<[]>, never>>,
  Expect<Equal<First<[undefined]>, undefined>>
];

所以对于上面的不同解法,对 number[] 这种情况,返回 numbernever 都在可理解范围。

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