字节大小的 TypeScript #1 - 过滤器类型
嗨!欢迎阅读我的全新 TypeScript 博文系列的第一篇。
在本系列中,我将演示和解构字节大小的 TypeScript 片段,以帮助您了解如何编写类型级代码。
我们开始吧,好吗?
过滤器类型
在 TypeScript 中,我们有一个元组类型的概念,它基本上与 JS 中的数组直接对应。它看起来像这样:
type MyTuple = [1, 2, string, boolean, null];
现在假设我们想在 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]
第 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
您还会注意到这...infer Rest
部分,它类似于 JavaScript 的 rest 语法。这部分将推断数组中的其余元素,并将其保存在名为 的变量中,Rest
您也可以在条件分支中访问该变量,在本例中,它将具有 的值[2, 3]
。
第 5、6、7 行:
现在我们处于条件类型的 true 分支中
我们正在FirstElement extends FilterBy
检查是否FirstElement
等于FilterBy
如果上述条件满足,我们将返回一个新的元组类型[FirstElement, ...Filter<Rest, FilterBy>]
,这里我们包括&,然后使用元素再次FirstElement
递归调用泛型。Filter
Rest
请注意,我们还使用了扩展语法
...Filter<Rest, FilterBy>
,因为 Filter 泛型返回一个元组,所以我们可以在我们返回的这个新元组中扩展它的值。
如果FirstElement extends FilterBy
失败,我们将再次递归调用过滤器,但跳过FirstElement
这次Filter<Rest, FilterBy>
。
现在让我们逐步执行每个递归调用的代码,以更好地理解发生了什么:
// Initial state
type D = Filter<[1, 'hello', 'world'], number>
递归#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;
递归#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;
递归#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;
递归#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.
就是这样,我希望你现在理解了 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]
很酷不是吗?
无论如何,我想就是这样,我希望你学到了一些新东西并且喜欢这篇文章。
我将继续这个博客文章系列,因此请务必在twitter或 dev.to上关注我以获取最新信息。
鏂囩珷鏉ユ簮锛�https://dev.to/anuraghazra/byte-sized-typescript-1-filter-type-3ga5