awesome-typescript
awesome-typescript copied to clipboard
「重学TS 2.0 」TS 练习题第三十三题
实现一个 ToNumber
工具类型,用于实现把数值字符串类型转换为数值类型。具体的使用示例如下所示:
type ToNumber<T extends string> = // 你的实现代码
type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20
请在下面评论你的答案
首先利用 TS 模板字符串把默认数组 S 的长度转为字符串,之后那这个字符串和 T 比较,若相等,表示当前 S 的长度和传入的字符串所对应的数值相等 (如,"15" --> 15),否则增加 S 的长度,进行下一次 ToNumber 判断
不过这种方式可能会出现一个警告:Type instantiation is excessively deep and possibly infinite. ts(2589)
,因为字符串过大时,可能会导致递归次数变多,个人认为可以忽略此错误
基本上,这种和数组有关系的类型,都需要构建一个辅助数组来进行判断
type ToNumber<T extends string, S extends any[] = [], L extends number = S['length']> =
`${L}` extends T ? L : ToNumber<T, [...S, 1]>
// 用例
type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20
一个新的思路,感觉有点复杂了,不过比之前的最大限制提升了一些
// 10进制数,递归数不超过10,解决了递归次数过多的报错问题
type GetNumber<T extends any, S extends any[] = [], L extends number = S['length']> =
`${L}` extends T ? L : GetNumber<T, [...S, 1]>
// 字符串转反向数组, 方便从后向前构建目标数组, 如: '1234' -> ['4', '3', '2', '1']
type StrToReverseArr<T extends string> =
T extends `${infer R1}${infer R2}` ? R2 extends '' ? [R1] : [...StrToReverseArr<R2>, R1] : []
/**
* 辅助矩阵, 用于构建当前位数的值, 那上面的 ['4', '3', '2', '1'] 来说, 相当于:
*
* 个位数: 4 --> 构建一个长度为 4*0001 的数组, Matrix<[1], 4>
* 十位数: 3 --> 构建一个长度为 3*0010 的数组, Matrix<Matrix<[1], 10>, 3>
* 百位数: 2 --> 构建一个长度为 2*0100 的数组, Matrix<Matrix<Matrix<[1], 10>, 10>, 2>
* 千位数: 1 --> 构建一个长度为 1*1000 的数组, Matrix<Matrix<Matrix<Matrix<[1], 10>, 10>, 10> , 1>
*/
type Matrix<T extends any[], I extends number> = [
[],
[...T],
[...T, ...T],
[...T, ...T, ...T],
[...T, ...T, ...T, ...T],
[...T, ...T, ...T, ...T, ...T],
[...T, ...T, ...T, ...T, ...T, ...T],
[...T, ...T, ...T, ...T, ...T, ...T, ...T],
[...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T],
[...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T],
[...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T],
][I]
// 生成并合并所有位数的数组
type GetCurArr<
T extends string, // 初始参数, 只在第一次执行时生效, 后面用空字符串 '' 占位
A extends any[] = StrToReverseArr<T>, // 反转后的数组, 只在第一次时通过计算得到, 后面都是递归传入
D extends any[] = [1], // 当前位数的单位长度, 核心是通过这个构建每位所对应的数组, 然后合并他们
> = A extends [infer R1, ...infer R2]
? [...GetCurArr<'', R2, Matrix<D, 10>>, ...Matrix<D, GetNumber<R1>>]
: []
type ToNumber<T extends string> = GetCurArr<T>['length']
// 用例
type T0 = ToNumber<"123">; // 123
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"999">; // 999
// type T3 = GetCurArr<"1999">;
// 还是没有完全解决递归的问题, 超过 1000 会报 "Type produces a tuple type that is too large to represent.(2799)"
type ToNumber<T extends string, A extends any[] = []> = `${A["length"]}` extends T
? A["length"]
: ToNumber<T, [...A, '']>;
type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20
思路: ts里运算很匮乏,并没有直接的数字运算,这里巧妙的利用了数组长度来实现,通过递归构造数组,使得构造出来的数组长度和期望的匹配,主要要把数组长度通过字符串模板的方式转换成字符串。否则永远匹配不成功
type ToNumber<
T extends string,
A extends any[] = []
> = `${A['length']}` extends T ? A['length'] : ToNumber<T, [...A, T]>
type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20
// 这个O就算是个数字也可以扩展才对,这就是最佳解法
type ToNumber<T extends string> = T extends `${infer O extends number}` ? O : never
type T0 = ToNumber<"0">; // 0
type T1 = ToNumber<"10">; // 10
type T2 = ToNumber<"20">; // 20