TypeScript 类型体操之 First of Array

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 中,我们通过 extendsinfer 可以实现一个类似于模式匹配的效果。前面说过 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[] 这种情况,返回 numbernever 都在可理解范围。

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>>
];
相关推荐
上官花雨32 分钟前
什么是axios?怎么使用axios封装Ajax?
前端·ajax·okhttp
米奇妙妙wuu33 分钟前
React中 setState 是同步的还是异步的?调和阶段 setState 干了什么?
前端·javascript·react.js
李刚大人35 分钟前
react-amap海量点优化
前端·react.js·前端框架
闹闹没有闹1 小时前
socket连接封装
前端
qq_364371722 小时前
Vue 内置组件 keep-alive 中 LRU 缓存淘汰策略和实现
前端·vue.js·缓存
y先森3 小时前
CSS3中的弹性布局之侧轴的对齐方式
前端·css·css3
y先森8 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy8 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189118 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿9 小时前
CSS查缺补漏(补充上一条)
前端·css