TypeScript类型实现数组flat

前段时间在知乎看到大佬用typescript类型运算实现一个中国象棋程序, 才知道typescript可以这样玩。

image.png

看了下大佬的实现,发现了有个infer操作符,不知道是干嘛的。查了下infer 表示在 extends 条件语句中待推断的类型变量。

image.png

上面是typescript系统内置的ReturnType,可以推断出数据的类型,其中infer R可以理解为一个占位,也是返回的类型。

image.png

infer

知道infer的作用后我们来练习下用法

  • 取数组中的第一个元素
  • hello world中的hello

取数组中的第一个元素

type 数组1 = [686, 999];
type 获取数组的第一个元素<任意数组 extends any[]> = 任意数组 extends [
  第一个元素: infer 数组的第一个元素,
  ...剩余的元素: infer 剩余的数组
]
  ? 数组的第一个元素
  : any;

type 第一个元素是 = 获取数组的第一个元素<数组1>;
复制代码

测试下, 拿到了686。

image.png

hello world中的hello

type 你好世界 = 'hello world';
type 获取世界 <字符串 extends string> = 字符串 extends `${infer R} world` ? R : any;
type 世界 = 获取世界<你好世界>
复制代码

image.png

上面两个例子,说明了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的数组>
复制代码

image.png

类型生成depth长度的数组

那么我们只要有一个类型生成任意长度数组,就可以表示一个数字了。这里只需要递归遍历下即可

type 生成对应长度的空数组<长度, 结果数组 extends any[] = []> = 结果数组["length"] extends 长度
    ? 结果数组
    : 生成对应长度的空数组<长度, [any, ...结果数组]>;

type 长度为3的数组 = 生成对应长度的空数组<3>;
复制代码

image.png

类型减一表示depth-1

要实现depth - 1的操作,不就是depth长度的数组减去一个元素,变成depth - 1长度的数组。这里就用到了infer

// 使用解构把数组的第一个元素去掉
type 数组长度减一<数组 extends any[] = []> = 数组 extends [
    第一个元素: infer 数组的第一个元素,
    ...剩余的元素: infer 剩余的数组
] ? 剩余的数组 : 数组;

type 减一<N extends number> = 获取数组长度<数组长度减一<生成对应长度的空数组<N>>>;
type 九 = 减一<10>
复制代码

image.png

这样就实现了depth - 1

实现flat拉平任意层级数组

有了上面的类型,就可以根据flat函数的实现原理来完成typescript版本的flat了,主要对着函数逻辑来extends实现if else语句

image.png

type 拉平任意层级数组<
  多维数组 extends any[],
  层数 extends number = 1
> = 层数 extends 0
  ? 获取数组长度<多维数组> extends 0
    ? 多维数组
    : 多维数组
  : 多维数组 extends [
      第一个元素: infer 数组的第一个元素,
      ...剩余的元素: infer 剩余的数组
    ]
  ? 数组的第一个元素 extends any[]
    ? [
        ...拉平任意层级数组<数组的第一个元素, 减一<层数>>,
        ...拉平任意层级数组<剩余的数组, 层数>
      ]
    : [数组的第一个元素, ...拉平任意层级数组<剩余的数组, 层数>]
  : 多维数组;
复制代码

来个复杂的数组拉平两层测试下, OK

image.png

完整代码

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)];
};
复制代码

猜你喜欢

转载自juejin.im/post/7042712223382241288