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>>
];
相关推荐
朦胧之5 分钟前
AI 编程工具使用浅谈
前端·后端
柳杉9 分钟前
HTML-in-Canvas:让 Canvas 完美渲染 HTML 的 Web 新标准
前端·javascript
cTz6FE7gA16 分钟前
WebGL实战:用Three.js创建3D场景,实现沉浸式Web体验
前端·javascript·webgl
We་ct27 分钟前
LeetCode 69. x 的平方根:两种解法详解
前端·javascript·算法·leetcode·typescript·平方
qq. 280403398443 分钟前
数据结构引论
前端·数据结构
daad77744 分钟前
WSL2_wifi驱动安装
开发语言·前端·javascript
ZC跨境爬虫1 小时前
Scrapy实战爬取5sing网站:Pipeline优化+全流程踩坑复盘,从报错到数据落地
前端·爬虫·python·scrapy
牛马1111 小时前
Flutter BoxDecoration
前端·javascript·flutter
M ? A2 小时前
VuReact 编译器核心重构:统一管理组件元数据收集
前端·javascript·vue.js·react.js·重构·开源
山海AI手册2 小时前
030、AI应用前端展示:Streamlit快速构建交互式Web应用
前端·人工智能