Yuzhe's Blog

yuzhes

去除数组指定元素

去除数组指定元素

题目

实现一个像 Lodash.without 函数一样的泛型 Without<T, U>,它接收数组类型的 T 和数字或数组类型的 U 为参数,会返回一个去除 U 中元素的数组 T。

例如:

type Res = Without<[1, 2], 1>; // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []

解答

俺在这提供了两种解法。

Solution #1

设想下面的场景:

type Res = Without<[1, 2, 4, 1, 5], [1, 2]>

俺们假设 T[1, 2, 4, 1, 5], U[1, 2], 俺们需要去除 U 中的元素, 也就是 [1, 2], 期望的结果是 [4, 5].

一个直觉的思路是: 遍历 T, 如果当前元素不在 U 中, 就保留, 否则就去除.

于是俺们想到了俺们之前写的工具类型 Includes, 它可以判断一个元素是否在一个数组中. 如果您忘记了, 请参考 Includes

Solution #2

考虑到 Solution #1 中的解法, 俺们可以使用了Includes来检查元素是否在 U 中. Includes接收一个数组类型的 T 和一个元素类型的 U, 返回一个布尔值, 表示 U 是否在 T 中.

俺们是不是能找到一个更简单的表达方式呢?

试想这样的表达式:

type t = 1 extends 1 | 2 | 3 ? true : false

这个表达式的结果是 true, 因为 11 | 2 | 3 中.

这是因为 extends 操作符在这里被用来判断一个类型是否是另一个类型的子集. 而联合类型是由多个类型组成的, 只要是其中任一类型, 都会返回 true.这恰好就是俺们需要的.

如果俺们能把需要排除的数组转换为联合类型, 那么俺们就可以很方便的使用 extends 来判断原数组的元素是否在 U 中.

接下来, 俺们需要一个工具类型, 将数组转换为联合类型.

type ToUnion<T> = T extends any[] ? T[number] : T

ToUnion 接收一个数组类型的 T, 返回一个联合类型, 由 T 中的元素组成. 符合直觉的, T[number] 可以获取数组中的元素类型的联合类型.

有了 ToUnion, 俺们就可以很方便的判断元素是否在 U 中了.

这里俺们依旧使用递归的方式, 逐个判断元素是否在 U 中, 如果不在, 就保留, 否则就去除.

如果您对递归的检查数组不熟悉, 请参考 Includes的写法.

type Without<T, U> = 
  T extends [infer F, ...infer R] // 取出数组的第一个元素F
    ? F extends ToUnion<U> // 判断F是否在U中
      ? Without<R, U> // 如果在, 递归检查剩余的元素
      : [F, ...Without<R, U>] // 如果不在, 保留F, 递归检查剩余的元素
    : T // 如果T为空数组, 返回空数组