前段时间在知乎看到大佬用typescript类型运算实现一个中国象棋程序, 才知道typescript可以这样玩。
看了下大佬的实现,发现了有个一直没用过的infer操作符。查了下infer 表示在 extends 条件语句中待推断的类型变量。
上面是typescript系统内置的ReturnType,可以推断出数据的类型,其中infer R可以理解为一个占位,也是返回的类型。
infer
知道infer的作用后我们来练习下用法
- 取数组中的第一个元素
- 取
hello world中的hello
取数组中的第一个元素
type 数组1 = [686, 999];
type 获取数组的第一个元素<任意数组 extends any[]> = 任意数组 extends [
第一个元素: infer 数组的第一个元素,
...剩余的元素: infer 剩余的数组
]
? 数组的第一个元素
: any;
type 第一个元素是 = 获取数组的第一个元素<数组1>;
测试下, 拿到了686。
取hello world中的hello
type 你好世界 = 'hello world';
type 获取世界 <字符串 extends string> = 字符串 extends `${infer R} world` ? R : any;
type 世界 = 获取世界<你好世界>
上面两个例子,说明了infer 表示在 extends 条件语句中待推断的类型变量
flat拍平数组
flat指拉平任意层数的数组,默认拉平一层
[1, [2, [3]]].flat() // [1, 2, [3]]
先学习下flat的实现, 我写了以下两种。
// 遍历加递归
function flat(arr, depth = 1) {
if (depth === 0) return arr;
return arr.reduce((r, c) => {
if (Array.isArray(c)) {
return [...r, ...flat(c, depth - 1)];
}
return [...r, c];
}, []);
}
// 递归+解构
function flat(arr, depth = 1) {
if (depth === 0 || arr.length === 0) return arr;
const [t, ...rest] = arr;
if (Array.isArray(t)) {
return [
...flat(t, depth - 1),
...flat(rest, depth)
];
}
return [t, ...flat(rest, depth)];
}
typescript没有可以直接像reduce遍历的类型,因此只能用第二种方式来搞。
问题拆解
- 用数组['length']表示
depth - 类型生成
depth长度的数组 - 类型减一表示
depth-1
用数组['length']表示depth
上面代码中会有depth - 1的操作,但是在typescript中类型是不能直接减一的,这里可以使用一个技巧,通过数组的length表示长度。
type 长度是3的数组 = [any, any, any]
type 获取数组长度<数组 extends any[]> = 数组['length'];
type 三 = 获取数组长度<长度是3的数组>
类型生成depth长度的数组
那么我们只要有一个类型生成任意长度数组,就可以表示一个数字了。这里只需要递归遍历下即可
type 生成对应长度的空数组<长度, 结果数组 extends any[] = []> = 结果数组["length"] extends 长度
? 结果数组
: 生成对应长度的空数组<长度, [any, ...结果数组]>;
type 长度为3的数组 = 生成对应长度的空数组<3>;
类型减一表示depth-1
要实现depth - 1的操作,不就是depth长度的数组减去一个元素,变成depth - 1长度的数组。这里就用到了infer
// 使用解构把数组的第一个元素去掉
type 数组长度减一<数组 extends any[] = []> = 数组 extends [
第一个元素: infer 数组的第一个元素,
...剩余的元素: infer 剩余的数组
] ? 剩余的数组 : 数组;
type 减一<N extends number> = 获取数组长度<数组长度减一<生成对应长度的空数组<N>>>;
type 九 = 减一<10>
这样就实现了depth - 1。
实现flat拉平任意层级数组
有了上面的类型,就可以根据flat函数的实现原理来完成typescript版本的flat了,主要对着函数逻辑来extends实现if else语句
type 拉平任意层级数组<
多维数组 extends any[],
层数 extends number = 1
> = 层数 extends 0
? 获取数组长度<多维数组> extends 0
? 多维数组
: 多维数组
: 多维数组 extends [
第一个元素: infer 数组的第一个元素,
...剩余的元素: infer 剩余的数组
]
? 数组的第一个元素 extends any[]
? [
...拉平任意层级数组<数组的第一个元素, 减一<层数>>,
...拉平任意层级数组<剩余的数组, 层数>
]
: [数组的第一个元素, ...拉平任意层级数组<剩余的数组, 层数>]
: 多维数组;
来个复杂的数组拉平两层测试下, OK
完整代码
type 生成对应长度的空数组<
长度,
结果数组 extends any[] = []
> = 结果数组["length"] extends 长度
? 结果数组
: 生成对应长度的空数组<长度, [any, ...结果数组]>;
type 数组长度减一<数组 extends any[] = []> = 数组 extends [
第一个元素: infer 数组的第一个元素,
...剩余的元素: infer 剩余的数组
]
? 剩余的数组
: 数组;
type 减一<N extends number> = 获取数组长度<
数组长度减一<生成对应长度的空数组<N>>
>;
type 拉平任意层级数组<
多维数组 extends any[],
层数 extends number = 1
> = 层数 extends 0
? 获取数组长度<多维数组> extends 0
? 多维数组
: 多维数组
: 多维数组 extends [
第一个元素: infer 数组的第一个元素,
...剩余的元素: infer 剩余的数组
]
? 数组的第一个元素 extends any[]
? [
...拉平任意层级数组<数组的第一个元素, 减一<层数>>,
...拉平任意层级数组<剩余的数组, 层数>
]
: [数组的第一个元素, ...拉平任意层级数组<剩余的数组, 层数>]
: 多维数组;
type 数组 = 拉平任意层级数组<[[0], [1, [2, [[3, [4]]], [5, [6], 7]]], [8, [9]], 10], 2>;
优化
上面的减一实现最多只能100,然后递归就爆了, 换了一种方式实现类型生成对应长度的数组, 最大支持到生成9999长度的数组
type 生成对应长度的空数组<
长度 extends string,
结果数组 extends any[] = []
> = `${结果数组["length"]}` extends 长度
? 结果数组
: 生成对应长度的空数组<长度, [any, ...结果数组]>;
// 打表
type 字符串数字 = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
// 按照乘法*10来叠加数组长度
// 11 = 1 * 10 + 1
// 111 = 10 * 10 + 1 * 10 + 1
type 创建对应长度的数组<长度 extends string, 结果数组 extends any[] = []> =
长度 extends `${infer 第一个数字}${infer 剩余数字}`
? 第一个数字 extends 字符串数字
? 创建对应长度的数组<剩余数字, [...结果数组, ...结果数组, ...结果数组, ...结果数组,
...结果数组, ...结果数组, ...结果数组, ...结果数组, ...结果数组, ...结果数组,
...生成对应长度的空数组<第一个数字>]>
: never
: 结果数组
type 数组长度减一<数组 extends any[] = []> = 数组 extends [
第一个元素: infer 数组的第一个元素,
...剩余的元素: infer 剩余的数组
]
? 剩余的数组
: 数组;
type 减一<长度 extends number> = 获取数组长度<
数组长度减一<创建对应长度的数组<`${长度}`>>
>;
type 拉平任意层级数组<
多维数组 extends any[],
层数 extends number = 1
> = 层数 extends 0
? 获取数组长度<多维数组> extends 0
? 多维数组
: 多维数组
: 多维数组 extends [
第一个元素: infer 数组的第一个元素,
...剩余的元素: infer 剩余的数组
]
? 数组的第一个元素 extends any[]
? [
...拉平任意层级数组<数组的第一个元素, 减一<层数>>,
...拉平任意层级数组<剩余的数组, 层数>
]
: [数组的第一个元素, ...拉平任意层级数组<剩余的数组, 层数>]
: 多维数组;
type 数组 = 拉平任意层级数组<[[0], [1, [2, [[3, [4]]], [5, [6], 7]]], [8, [9]], 10], 2>;
last-modify: 2022-01-10 更新
最后
上面的实现最关键是数字怎么增加或者减少和合理的函数逻辑。如果不熟悉可以先从简单点的入手,比如直接拉平为一维数组。拉平为一维数组的函数实现, 感兴趣可以使用infer实现下。
const flatern = (arr) => {
if (arr.length === 0) return arr;
const [top, ...rest] = arr;
if (Array.isArray(top)) {
return flatern([...top, ...rest]);
}
return [top, ...flatern(rest)];
};