字节大小的 TypeScript #1 - 过滤器类型

2025-06-08

字节大小的 TypeScript #1 - 过滤器类型

嗨!欢迎阅读我的全新 TypeScript 博文系列的第一篇。

在本系列中,我将演示和解构字节大小的 TypeScript 片段,以帮助您了解如何编写类型级代码。


我们开始吧,好吗?

过滤器类型

在 TypeScript 中,我们有一个元组类型的概念,它基本上与 JS 中的数组直接对应。它看起来像这样:

type MyTuple = [1, 2, string, boolean, null];
Enter fullscreen mode Exit fullscreen mode

现在假设我们想在 Type Level 中实现一个Filter类似于 JavaScript 原生的方法Array.Filter,我们该怎么做呢?

这是完整的实现:
随着我们继续,我将逐行解释这种类型。

type Filter<Arr extends unknown[], FilterBy> = Arr extends [
  infer FirstElement,
  ...infer Rest
]
  ? FirstElement extends FilterBy
    ? [FirstElement, ...Filter<Rest, FilterBy>]
    : Filter<Rest, FilterBy>
  : Arr;

type Demo = Filter<[1, 2, string, boolean], number>
// -> [1, 2]
Enter fullscreen mode Exit fullscreen mode

在操场上开放

第 1 行:

在第一行我们有Arr extends [ infer FirstElement, ...infer Rest]

这里我们说的是:
如果Arr等于,[infer FirstElement, ...infer Rest]则进入条件类型的 true 分支。这一行代码还会确保传递的Arr实际上是 Tuple。如果不是,则会进入 false 分支,也就是Arr在最后一行返回 。

重要的是infer关键字 infer,它是 TypeScript 的检查镜,它可以提取特定位置的类型值。

基本上,我们是在询问 TypeScript 编译器:“嘿!无论这个地方是什么,都把它赋值给这个名为 的变量。FirstElement

FirstElement然后我们可以在条件分支中访问该类型的值。

让我们看一个例子:

type GetFirstElementOfArray<Arr extends any[]> = 
  Arr extends [infer FirstElement, ...infer Rest] 
    ? FirstElement 
    : never

type Demo = GetFirstElementOfArray<[1, 2, 3]> // 1
Enter fullscreen mode Exit fullscreen mode

您还会注意到这...infer Rest部分,它类似于 JavaScript 的 rest 语法。这部分将推断数组中的其余元素,并将其保存在名为 的变量中,Rest您也可以在条件分支中访问该变量,在本例中,它将具有 的值[2, 3]

第 5、6、7 行:

现在我们处于条件类型的 true 分支中

突出显示第 5、6 和 7 行的 Filter 类型的屏幕截图

我们正在FirstElement extends FilterBy检查是否FirstElement等于FilterBy

如果上述条件满足,我们将返回一个新的元组类型[FirstElement, ...Filter<Rest, FilterBy>],这里我们包括&,然后使用元素再次FirstElement递归调用泛型。FilterRest

请注意,我们还使用了扩展语法...Filter<Rest, FilterBy>,因为 Filter 泛型返回一个元组,所以我们可以在我们返回的这个新元组中扩展它的值。

如果FirstElement extends FilterBy失败,我们将再次递归调用过滤器,但跳过FirstElement这次Filter<Rest, FilterBy>

现在让我们逐步执行每个递归调用的代码,以更好地理解发生了什么:

过滤器类型逐步递归状态可视化

// Initial state
type D = Filter<[1, 'hello', 'world'], number>
Enter fullscreen mode Exit fullscreen mode

递归#1:

// input <[1, 'hello', 'world'], number>
type Filter<Arr extends unknown[], FilterBy> = Arr extends [
  infer FirstElement, // 1
  ...infer Rest // ['hello', 'world']
]
  ? FirstElement extends FilterBy // 1 == number -> true
    ? [FirstElement, ...Filter<Rest, FilterBy>] // [1, ...Filter<['hello', 'world'], FilterBy>]
    : Filter<Rest, FilterBy>
  : Arr;

Enter fullscreen mode Exit fullscreen mode

递归#2:

// input <['hello', 'world'], number>
type Filter<Arr extends unknown[], FilterBy> = Arr extends [
  infer FirstElement, // 'hello'
  ...infer Rest // ['world']
]
  ? FirstElement extends FilterBy // 'hello' == number -> false
    ? [FirstElement, ...Filter<Rest, FilterBy>]
    : Filter<Rest, FilterBy>  // Filter<['world'], FilterBy>
  : Arr;
Enter fullscreen mode Exit fullscreen mode

递归#3:

// input <['world'], number>
type Filter<Arr extends unknown[], FilterBy> = Arr extends [
  infer FirstElement, // 'world'
  ...infer Rest // []
]
  ? FirstElement extends FilterBy // 'world' == number -> false
    ? [FirstElement, ...Filter<Rest, FilterBy>]
    : Filter<Rest, FilterBy>  // Filter<[], FilterBy>
  : Arr;
Enter fullscreen mode Exit fullscreen mode

递归#4:

// input <[], number>
type Filter<Arr extends unknown[], FilterBy> = Arr extends [
  infer FirstElement, // unknown
  ...infer Rest // unknown
]
  ? FirstElement extends FilterBy
    ? [FirstElement, ...Filter<Rest, FilterBy>]
    : Filter<Rest, FilterBy>
  : Arr;
  // ^ false branch is taken since `Arr extends [infer FirstElement, ...infer Rest]` failed,
 //   thus empty Arr is returned.
Enter fullscreen mode Exit fullscreen mode

就是这样,我希望你现在理解了 Filter 类型的工作原理。它的核心很简单,但理解每个部分的工作原理才是关键。

如果您仍然无法理解类型级别的代码,我们如何将类型级别的代码重写为运行时 javascript 代码?

你会惊讶地发现它们看起来多么相似:

function Filter(Arr, FilterBy) {
    if (Arr.length < 1) return Arr;
    const [FirstElement, ...Rest] = Arr;
    if (typeof FirstElement === FilterBy) {
        return [FirstElement, ...Filter(Rest, FilterBy)];
    } else {
        return Filter(Rest, FilterBy)
    }
}

Filter([1, 2, 3, 'hello', true], 'number')
// [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

很酷不是吗?

无论如何,我想就是这样,我希望你学到了一些新东西并且喜欢这篇文章。

我将继续这个博客文章系列,因此请务必在twitter或 dev.to上关注我以获取最新信息。

鏂囩珷鏉ユ簮锛�https://dev.to/anuraghazra/byte-sized-typescript-1-filter-type-3ga5
PREV
像专业人士一样过滤数组
NEXT
Vscode 中的透明背景