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>>
];
相关推荐
彭于晏爱编程7 小时前
🐻 Zustand 使用指南:从 0 到精通的最快路线
前端
장숙혜7 小时前
Vue DevTools 速通-掌握开发调试器
前端·javascript·vue.js
谢尔登8 小时前
为什么React 17开始无需在组件中引入React了?
前端·react.js·前端框架
ohyeah8 小时前
JavaScript 面向对象的本质:从对象模板到组合继承的完整演进
前端·javascript
Drift_Dream8 小时前
虚拟滚动:优化长列表性能的利器
前端
逃离疯人院8 小时前
前端性能深度解析:网络响应时间与实际渲染时间的鸿沟
前端
我是若尘8 小时前
🚀 深入理解 Claude Code:从入门到精通的能力全景图
前端
老前端的功夫8 小时前
Webpack 深度解析:从配置哲学到编译原理
前端·webpack·前端框架·node.js
重铸码农荣光8 小时前
🌟 Vibe Coding 时代:用自然语言打造你的专属 AI 单词应用
前端·vibecoding
MegatronKing8 小时前
SSL密钥协商导致抓包失败的原因分析
前端·https·测试