TypeScript 类型体操之 First of Array
题目要求
实现一个通用
First<T>
,它接受一个数组T
并返回它的第一个元素的类型。
例如:
ts
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
ts
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 []
ts
type First<T extends any[]> = T extends [] ? never : T[0]
和第一种实现方式类型,不过使用了 T extends []
来判断了 T
类型是否为空数组。
3、T[number] extends never
ts
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
ts
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 中,我们通过 extends
和 infer
可以实现一个类似于模式匹配的效果。前面说过 A extends B ? C : D
的意思是"如果 A 可以赋值给 B,那么类型就是 C,否则就是 D"。而在 B
中我们把某个类型变量使用 infer T
来表示,并可以获取它。
举个例子:
ts
// 我们使用 (...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>')
我们使用 ()
包裹要获取的内容,当整个正则和字符串匹配的时候,我们就能获取真正对应的字符串了。
现在我们可以实现题目
ts
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是针对类型的编程,而不是针对数据的编程。
在做这道题的时候一直纠结一个问题
ts
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[]
来说,它并不一定有第一个元素,所以我们也没办法获取第一个元素的类型。所以个人认为这是一个未定义的结果。而对于测试样例,我们也能看到测试的类型都是具体的类型。所以对于上面的不同解法,对 number[]
这种情况,返回 number
或 never
都在可理解范围。
ts
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>>
];