这是 TypeScript 大挑战系列 的第 2 篇,笔者计划用 6 个月时间完成 type-challenges 项目中的 133 个挑战并记录下自己的收获,目前还剩余 130 个,截止日期为 2023 年 1 月25 日。
4. 数组第一个元素
挑战内容
实现一个通用 First<T>,它接受一个数组 T 并返回它第一个元素的类型。
// TODO: 补充 First 代码
type First<T extends any[]> = any
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // 为 'a'
type head2 = First<arr2> // 为 3
type head3 = First<[]] // 为 never
知识点:
infer / never
题目解析
为了取得数组第一个元素的类型,自然而然的想法就是通过 T[0] 这种下标的方式访问,但是这样会存在一个问题:对于空数组,得到的类型为 undefined 而不是 never,例如:
type First<T extends any[]> = T[0]
type head = First<[]> // 为 undefined
然而题目要求对于空数组返回 never,这也是更合理的行为,因为此时的确不存在第一个元素(对应 never 的含义),那我们该怎么做呢?
可能有同学会想出 type First<T extends any[]> = T['length'] >= 1 ? T[0] : never 这样的尝试,但是 T['length'] 拿到的其实是长度 number 这个类型,而不是 T 的长度这个值,number 这个类型是没办法直接和值 1 进行比较的。
像这种在条件语句中拿到待推断类型的场景,就需要用到 infer 了。它和 extends 搭配在一起,能够在 X extends Y 为 true 的情况下,使用通过 infer 推断得到的类型,例如:
type ArrayElementType<T>
= T extends (infer E)[] ? E : never;
// item1 为 number
type item1 = ArrayElementType<number[]>
// item2 为 never
type item2 = ArrayElementType<number>
题目答案
关键点在于将 any[] 解构成 [infer Head, ...any[]],这样就可以拿到第一个元素的类型,并在 Head 不存在的时候返回 never:
type First<T extends any[]>
= T extends [infer Head, ...any[]]
? Head : never
5. 获取元组长度
挑战内容
创建一个通用的 Length,接受一个 readonly 的数组,返回这个数组的长度:
// TODO: 实现 Length
type Length<T> = any
type tesla = ['tesla']
type spaceX = [
'FALCON 9',
'FALCON HEAVY',
]
// teslaLength 为 1
type teslaLength = Length<tesla>
// spaceXLength 为 2
type spaceXLength = Length<spaceX>
知识点:
T['length'] / readonly
题目解析
在上一题 4. 数组第一个元素 已经提到:可以通过 T['length'] 拿到数组类型 T 的长度。
同时,在大挑战(一)中也提到:type tesla = ['tesla'] 这样固定不变的元组可以用 readonly 来修饰。
题目答案
type Length<T extends readonly any[] >
= T['length']
6. Exclude
挑战内容
实现内置的 Exclude <T, U> 类型,从联合类型 T 中排除 U 的类型成员,来构造一个新的类型:
// TODO: 实现 MyExclude
type MyExclude<T, U> = any
type Result = MyExclude<
'a' | 'b' | 'c' | 'd',
'a' | 'd'
> // 结果为 'b' | 'c'
知识点:
联合类型 / extends
题目解析
对于 T 这样的联合类型而言,作用于 extends 时,有一条独特的规则:
When conditional types act on a generic type, they become distributive when given a union type.
翻译一下就是:
在
X extends Y的条件类型语句中,若X是联合类型的范型,则会将联合类型的每一个可能的值代入进行独立计算,再将结果通过|组合起来。
举个例子,ToArray<string | number> 的结果其实是 ToArray<string> | ToArray<number:
type ToArray<Type>
= Type extends any ? Type[] : never
// StrArrOrNumArr 为 string[] | number[]
type StrArrOrNumArr = ToArray<
string | number
>
有一点需要强调的是:当且仅当 联合类型的范型 时才会有这种效果,如果单单是联合类型而不涉及泛型时,则是直接拿字面量进行判断:
type IsString<T>
= T extends string ? true : false
// isA 为 true | false
type isA = IsString<string | number>
// isB 为 false,此时不涉及泛型
type isB
= (string | number) extends string
? true : false
题目答案
由于 T 是联合类型,所以可以将 T 中的每一个类型取出,判断其是否在 U 中,如果在的话则通过 never 排除,不在的话则保留:
type MyExclude<T, U>
= T extends U ? never : T
结语
在完成了大挑战(一)之后,笔者明显感觉进行大挑战(二)的时候速度提升了,不知道大家是否也有这样的感觉呢🤔