题目
实现一个类型工具 First<T>,其中T是一个元组类型,返回这个元组的第1个元素的类型,如果元组是空的,返回 never 类型。
例如:
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type arr3 = [() => 1, { a:string }]
type arr4 = []
type head1 = First<arr1> // 返回字面量类型 'a'
type head2 = First<arr2> // 返回字面量类型 3
type head3 = First<arr3> // 返回字面量类型,是一个函数 `() => 1`
type head4 = First<arr4> // 返回类型 never
原题链接
思路
类比 JavaScript
类比 JavaScript,就是实现一个函数:
这个函数接收一个数组,返回这个数组的索引为0的元素,如果接收到的不是数组,就抛出错误,如果数组是空的,返回 never。
function getLength(arr) {
if (!Array.isArray(arr)) {
throw new TypeError(`Please pass an array.`);
}
if (arr.length === 0) {
return "never"
}
return arr[0];
}
提取逻辑点
1. 如果参数不是数组,抛出错误
1. 如果数组为空,返回 never
2. 返回数组中索引为0的元素
再把逻辑点翻译成 TypeScript 即可。
翻译成 TypeScript
用 typescript 实现上述逻辑点如下。
如果参数不是数组,抛出错误
在 TypeScript 世界里,抛出错误就是约束传参,因为传递错误 TS编译器 就会报错,和JS抛出错误是一样的效果。
要约束参数必须是数组,可以通过 extends 关键字,跟上 any[] 类型,如:
type OnlyAcceptArray<T extends any[]> = ...
如果数组为空,返回 never
T 是数组,如果直接用 T[0] 的话,TypeScript 返回的是 undefined 类型。
要想在空数组的时候返回 never,应该先判断一下,如果是空数组,显式返回 never。
T extends [] ? never : ...
返回数组中索引为0的元素
通过 T[0] 即可,如:
// tesla 类型是一个字面量类型,元组类型
type tesla = ['a', 'b', 'c', 'd']
// 得到 tesla 的第一个元素的字面量类型 'a'
type first = tesla[0]
实现
综上所述,最终的类型工具 First 实现为:
type First<T extends any[]> = T extends [] ? never : T[0];
进阶解法
进阶解法一、通过数组的 length 检查是否为空
- 通过
T["length"]获取数组长度 - 通过
extends检查长度是不是0
type First<T extends any[]> = T["length"] extends 0 ? never : T[0];
进阶解法二、利用 T[number] 检查空数组
如果数组为空,T[number] 返回 never,利用这一点来检查。
type First<T extends any[]> = T[number] extends never ? never : T[0];
进阶解法三、利用 infer 推断首元素类型
在 JavaScript 中,可以通过解构的方式获取数组的首元素:
function getFirst(arr) {
const [first, ...rest] = arr;
return first || "never";
}
如果首元素存在则返回首元素,如果首元素不存在则返回never。
翻译成 TypeScript
翻译成 TS,解构的首元素类型就是 infer F,剩余类型就是 infer Rest,infer 表示推断的意思,只有得到 T 以后才能推断出来类型。
如果 T 能够解构出 F,就说明 T 不是空数组,返回 F 类型即可;
如果 T 不能解构出 F,就说明 T 是空数组,返回 never。
type First<T extends any[]> = T extends [infer F, ...infer Rest] ? F : never;
总结知识点
extends 类型条件判断
extends 用来检查一个类型参数是不是某个给定的类型,如:
// 判断 T 是不是空数组类型
type isEmptyArray<T> = T extends [] ? true : false;
// 判断 T 是不是数组类型
type isArray<T> = T extends any[] ? true : false;
// 判断 T 是不是 ['a','b'] 这个元组字面量类型
type isTuple<T> = T extends ['a','b'] ? true : false;
// 判断 T 是不是布尔类型
type isBoolean<T> = T extends boolean ? true : false;
获取元组类型的 length
通过 T["length"] 可以获取到元组类型的长度。
infer 的使用方式
把 infer 当成 JS解构数组 来理解。