去除数组指定元素
去除数组指定元素
题目
实现一个像 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
, 因为 1
在 1 | 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为空数组, 返回空数组