前段时间在知乎看到大佬用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
条件语句中待推断的类型变量
falt拍平数组
falt
指拉平任意层数的数组,默认拉平一层
[1, [2, [3]]].flat() // [1, 2, [3]]
复制代码
先学习下falt
的实现, 我写了以下两种。
// 遍历加递归
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>;
复制代码
最后
上面的实现最关键是数字怎么增加或者减少和合理的函数逻辑。如果不熟悉可以先从简单点的入手,比如直接拉平为一维数组。拉平为一维数组的函数实现, 感兴趣可以使用infer
实现下。
const faltern = (arr) => {
if (arr.length === 0) return arr;
const [top, ...rest] = arr;
if (Array.isArray(top)) {
return faltern([...top, ...rest]);
}
return [top, ...faltern(rest)];
};
复制代码