typescript_如何掌握高级TypeScript模式

typescript

by Pierre-Antoine Mills

皮埃尔·安托万·米尔斯(Pierre-Antoine Mills)

如何掌握高级TypeScript模式 (How to master advanced TypeScript patterns)

了解如何为咖喱和Ramda创建类型 (Learn how to create types for curry and Ramda)

Despite the popularity of currying and the rise of functional programming (and of TypeScript), it is still a hassle today to make use of curry and have proper type checks. Even famous libraries like Ramda do not provide generic types for their curry implementations (but we will).

尽管currying的流行和函数式编程(以及TypeScript)的兴起,但如今使用curry并进行适当的类型检查仍然很麻烦。 即使像Ramda这样的著名图书馆也没有为其咖喱实现提供通用类型(但我们会提供)。

However, you need no functional programming background to follow this guide. The guide is about currying but it is only a topic of my choice to teach you advanced TypeScript techniques. You just need to have practised a bit with TypeScript’s primitive types. And by the end of this walk-through, you will be a real TS wizard ?.

但是,您不需要功能性编程背景即可阅读本指南。 该指南是关于currying的,但这只是我选择教高级TypeScript技术的主题。 您只需要对TypeScript的原始类型进行一些练习即可。 在本演练结束时,您将成为真正的TS向导?

If you’re a functional programmer, you are probably already using currying to create powerful compositions and partial applications… And if you are a bit behind, it’s time to take the leap into functional programming, start shifting away from the imperative paradigm and solve problems faster, with ease, and promote reusability within your codebase.

如果您是函数式程序员,则可能已经在使用curring来创建功能强大的合成和部分应用程序……如果您有些落后,是时候迈入函数式编程,开始远离命令式范式并解决问题了更快,更轻松地实现代码库中的可重用性。

At the end of this guide, you will know how to create powerful types like:

在本指南的最后,您将了解如何创建强大的类型,例如:

In fact, Ramda does have some kind of mediocre types for curry. These types are not generic, hard-coded, limiting us to a certain amount of parameters. As of version 0.26.x, it only follows a maximum of 6 arguments and does not allow us to use its famous placeholder feature very easily with TypeScript. Why? It’s hard, but we agree that we had enough and we’re going to fix this!

实际上,Ramda确实有一些普通的咖喱类型。 这些类型不是通用的, 硬编码的 ,将我们限制为一定数量的参数。 从0.26.x版本开始,它最多只能使用6个参数 ,并且不允许我们通过TypeScript轻松使用其著名的占位符功能。 为什么? 很难,但是我们同意我们已经足够了,我们将解决这个问题!

什么是咖喱? (What is currying?)

But before we start, let’s make sure that you have a very basic understanding of what currying is. Currying is the process of transforming a function that takes multiple arguments into a series of functions that take one argument at a time. Well that’s the theory.

但是,在开始之前,请确保您对什么是curry有一个非常基本的了解。 咖喱化是将一个包含多个参数的函数转换为一系列同时包含一个参数的函数的过程。 嗯,这就是理论。

I prefer examples much more than words, so let’s create a function that takes two numbers and that returns the result of their addition:

我更喜欢示例而不是单词,因此让我们创建一个接受两个数字并返回加法结果的函数:

The curried version of simpleAdd would be:

curry版本的simpleAdd将是:

In this guide, I will first explain how to create TypeScript types that work with a standard curry implementation.

在本指南中,我将首先说明如何创建与标准curry实现一起使用的TypeScript类型。

Then, we will evolve them into more advanced types that can allow curried functions to take 0 or more arguments.

然后,我们会将它们演化为更高级的类型 ,这些类型可以使咖喱函数接受0个或多个参数。

And finally, we will be able to use “gaps” that abstract the fact that we are not capable or willing to provide an argument at a certain moment.

最后,我们将能够使用“空白”来抽象化我们在某一时刻不具备或不愿提供论据的事实。

TL;DR: We will create types for “classic curry” & “advanced curry” (Ramda).

TL; DR :我们将为“经典咖喱”和“高级咖喱”(拉姆达)创建类型。

元组类型 (Tuple types)

Before we start learning the most advanced TypeScript techniques, I just want to make sure that you know tuples. Tuple types allow you to express an array where the type of a fixed number of elements is known. Let’s see an example:

在我们开始学习最先进的TypeScript技术之前,我只想确保您了解tuple 。 元组类型使您可以在已知固定数量元素类型的情况下表示一个数组。 让我们来看一个例子:

They can be used to enforce the kind of values inside a fixed size array:

它们可用于在固定大小的数组中强制使用值的种类:

And can also be used in combination of rest parameters (or destructuring):

并且还可以结合使用其余参数(或解构):

But before starting to build our awesome curry types, we’re going to do a bit of a warmup. We are going to create the first tools that we need to build one of the most basic curry types. Let’s go ahead.

但是在开始建立很棒的咖喱类型之前,我们将做一些热身。 我们将创建构建最基本的咖喱类型之一所需的第一个工具。 让我们继续。

Maybe you could guess… We are going to work with tuple types a lot. We’ll use them as soon as we extracted the parameters from the “original” curried function. So for the purpose of an example, let’s create a basic function:

也许您会猜到……我们将大量处理元组类型。 从“原始”咖喱函数中提取参数后,我们将立即使用它们。 因此,出于示例的目的,让我们创建一个基本函数:

We extracted the parameter types from fn00 thanks to the magic of Parameters. But it’s not so magical when you recode it:

fn00 Parameters的魔力,我们从fn00提取了参数类型。 但是当您重新编码时,它并不是那么神奇:

Let’s test it:

让我们测试一下:

Good, it works just as Parameters does. Don’t be scared of infer, it is one of the most powerful keywords for building types. I will explain it in more detail right after we practiced some more:

很好,它的工作方式与Parameters一样。 不要害怕infer ,它是构建类型最强大的关键字之一。 在我们进行更多实践之后,我将更详细地解释它:

Earlier, we learnt that a “classic curried” function takes one argument at a time. And we also saw that we can extract the parameter types in the form of a tuple type, very convenient. So Head takes a tuple type T and returns the first type that it contains. This way, we’ll be able to know what argument type has to be taken at a time.

较早之前,我们了解到“经典咖喱”函数每次仅接受一个参数。 而且我们还看到我们可以以元组类型的形式提取参数类型,非常方便。 所以Head接受一个元组类型T并返回它包含的第一个类型 。 这样,我们就能知道一次必须采用哪种参数类型。

Let’s test it:

让我们测试一下:

尾巴 (Tail)

A “classic curried” function consumes arguments one by one. This means that when we consumed the Head<Params&lt;F>>, we somehow need to move on to the next parameter that hasn’t been consumed yet. This is the purpose of Tail, it conveniently removes the first entry that a tuple might contain.

“经典咖喱”函数逐个使用参数。 这意味着当我们使用Head<Params& lt; F >>时,我们需要以某种方式移动到尚未使用的ne xt参数。 这是pur尾的姿态,它方便地删除第一个条目的元组可能包含。

As of TypeScript 3.4, we cannot “simply” remove the first entry of a tuple. So, we are going to work around this problem by using one very valid trick:

从TypeScript 3.4开始,我们不能“简单地”删除元组的第一个条目。 因此,我们将使用一个非常有效的技巧来解决此问题:

Using function types, we were able to tell TypeScript to infer the tuple that we wanted. If you do not understand it yet, it is not a problem, this is just a warmup, remember?

使用函数类型 ,我们能够告诉TypeScript推断我们想要的元组。 如果您还不了解它,那不是问题,这只是预热,还记得吗?

Let’s test it:

让我们测试一下:

HasTail (HasTail)

A curried function will return a function until all of it’s parameters have been consumed. This condition is reached when we called Tail enough times that there is no tail left, nothing’s left to consume:

直到它的所有的参数都已经消耗了咖喱函数会返回一个函数。 当我们调用Tail的次数足够多,以至于没有尾巴,没有东西可消耗时,便达到了这种条件:

Let’s test it:

让我们测试一下:

重要关键字 (Important keywords)

You have encountered three important keywords: type, extends and infer. They can be pretty confusing for beginners, so these are the ideas they convey:

您遇到了三个重要的关键字: typeextendsinfer 。 对于初学者来说,它们可能会让人很困惑,所以这些是他们传达的想法:

  • extends:

    extends

    To keep it simple, you are allowed to think of it as if it was our dear old

    为简单起见,您可以考虑一下它,就像它是我们亲爱的古老

    JavaScript’s

    JavaScript的

    ===. When you do so, you can see an extends statement as a simple ternary, and then it becomes much simpler to understand. In this case, extends is referred to as a conditional type.

    === 。 这样做时,您可以将extends语句视为简单的三元 ,然后它变得更易于理解。 在这种情况下, extends被称为条件类型

  • type:

    type

    I like to think of a type as if it was a

    我喜欢认为一种类型

    function, but for types. It has an input, which are types (called generics) and has an output. The output depends on the “logic” of a type, and extends is that block of logic, similar to an if clause (or ternary).

    功能 ,但适用于类型。 它有一个输入(类型)(称为Generic ),并有一个输出。 输出取决于类型的“逻辑”,并且extends到该逻辑块,类似于if子句(或三进制)。

  • infer:

    infer

    It is the magnifying glass of TypeScript, a beautiful inspecting tool that can

    它是TypeScript的放大镜,是一种漂亮的检查工具,可以

    extract types that are trapped inside different kinds of structures!

    提取陷在不同类型结构中的类型!

I think that you understand both extends & type well and this is why we are going to practice a bit more with infer. We’re going to extract types that are contained inside of different generic types. This is how you do it:

我认为您对extendstype都理解得很好,这就是为什么我们要在infer多做一些练习的原因。 我们将提取包含在不同泛型类型中的类型。 这是您的操作方式:

从对象中提取属性的类型 (Extract a property’s type from an object)

Let’s test it:

让我们测试一下:

Extract inner types from function types

从函数类型中提取内部类型

Let’s test it:

让我们测试一下:

Extract generic types from a class or an interface

从类或接口中提取泛型类型

Let’s test it:

让我们测试一下:

Extract types from an array

从数组中提取类型

Let’s test it:

让我们测试一下:

Extract types from a tuple

从元组中提取类型

Let’s test it:

让我们测试一下:

We tried to infer the type of the rest of the tuple into a type B but it did not work as expected. It is because TypeScript lacks of a feature that would allow us to deconstruct a tuple into another one. There is an active proposal that tackles these issues and you can expect improved manipulation for tuples in the future. This is why Tail is constructed the way it is.

我们试图将其余元组的类型推断为B型,但未按预期工作。 这是因为TypeScript 缺少一项功能,该功能使我们可以将一个元组解构为另一个元组。 有一个积极的提案可以解决这些问题,您可以期望将来对元组的操作得到改进。 这就是为什么以这种方式构造Tail原因。

infer is very powerful and it will be your main tool for type manipulation.

infer功能非常强大,它将成为您进行类型操作的主要工具

咖喱V0 (Curry V0)

The warm-up ? is over, and you have the knowledge to build a “classic curry”. But before we start, let’s summarize (again) what it must be able to do:

热身? 结束了,您就可以制作“经典咖喱”了。 但是在开始之前,让我们总结一下(再次)它必须具备的功能:

Our first curry type must take a tuple of parameters P and a return type R. It is a recursive function type that varies with the length of P:

我们的第一个咖喱类型必须采用参数 P的元组和返回类型R 这是一种递归函数类型, P长度 而变化

If HasTail reports false, it means that all the parameters were consumed and that it is time to return the return type R from the original function. Otherwise, there’s parameters left to consume, and we recurse within our type. Recurse? Yes, CurryV0 describes a function that has a return type of CurryV0 as long as there is a Tail (HasTail<P> extends true).

如果HasTail报告false ,它意味着所有的参数进行了消费 ,现在是时候回到返回类型R从原来的功能。 否则, 剩下的参数要消耗掉 ,我们在类型中递归 。 递归? 是的, CurryV0描述了一个函数,该函数的返回类型为CurryV0 ,只要有一个Tail ( HasTail<P> extend s true)即可​​。

This is as simple as it is. Here is the proof, without any implementation:

这很简单。 这是证明,没有任何实现:

But let’s rather visualize the recursion that happened above, step by step:

但是,让我们逐步地可视化上面发生的递归:

And of course, type hints work for an unlimited amount of parameters ?:

当然,类型提示可用于无限数量的参数?:

咖喱V1 (Curry V1)

Nice, but we forgot to handle the scenario where we pass a rest parameter:

很好,但是我们忘记处理传递rest参数的情况

We tried to use a rest parameter, but it won’t work because we actually expected a single parameter/argument that we earlier called arg0. So we want to take at least one argument arg0 and we want to receive any extra (optional) arguments inside a rest parameter called rest. Let’s enable taking rest parameters by upgrading it with Tail & Partial:

我们尝试使用rest参数,但是它无法正常工作,因为我们实际上期望我们之前称为arg0单个参数/参数。 因此,我们希望至少接受一个参数arg0 ,并且希望在称为rest的rest参数内接收任何其他(可选)参数。 让我们通过使用TailPartial进行升级来获取剩余参数:

Let’s test it:

让我们测试一下:

But we made a horrible mistake: the arguments are consumed very badly. According to what we wrote, this will not produce a single TS error:

但是我们犯了一个可怕的错误:争论被非常严重地消耗了。 根据我们写的内容,这不会产生单个TS错误:

In fact there is a big design problem because we said that we would force taking a single arg0. Somehow, we are going to need to keep track of the arguments that are consumed at a time. So, we will first get rid of arg0 and start tracking consumed parameters:

实际上存在一个很大的设计问题,因为我们说过将强制采用单个arg0 。 不知怎的,我们将需要持续跟踪在一段时间使用的参数。 因此,我们将首先摆脱arg0并开始跟踪消耗的参数:

There, we made use of a constrained generic called T that is going to track any taken arguments. But now, it is completely broken, there is no more type checks because we said that we wanted to track any[] kind of parameters (the constraint). But not only that, Tail is completely useless because it only worked well when we took one argument at a time.

在那里,我们使用了一个受约束的泛型T ,该泛型将跟踪任何采用的参数。 但是现在,它已经完全崩溃了,不再需要类型检查了,因为我们说过我们想跟踪any[]类型的参数(约束)。 不仅如此, Tail完全没有用,因为只有当我们一次提出一个论点时, Tail才能很好地工作。

There is only one solution: some more tools ?.

只有一种解决方案: 更多工具

递归类型 (Recursive types)

The following tools are going to be used to determine the next parameters to be consumed. How? By tracking the consumed parameters with T we should be able to guess what’s left.

以下工具将用于确定下一个要使用的参数。 怎么样? 通过使用T跟踪消耗的参数,我们应该能够猜测还剩下什么

Fasten your seat belt! You are about to learn another powerful technique ?:

系好安全带! 您将要学习另一种强大的技术吗?:

持续 (Last)

Take your time to try to understand this complex yet very short type. Thisexample takes a tuple as a parameter and it extracts its last entry out:

花点时间尝试理解这种复杂但很短的类型。 该示例将一个元组作为参数,并提取出最后一个条目:

Let’s test it:

让我们测试一下:

This example demonstrates the power of conditional types when used as an indexed type’s accessor. A what? A conditional type that accesses a type’s inner types in a command line fashion. For a more visual explanation:

此示例演示了用作索引类型的访问器时条件类型的强大功能。 什么啊 以命令行方式访问类型的内部类型的条件类型。 有关更直观的说明:

This technique is an ideal approach and a safe way to do recursion like we just did. But it is not only limited to recursion, it is a nice and a visual way to organise complex conditional types.

这种技术是一种理想的方法,并且像我们刚才一样是进行递归的安全方法。 但这不仅限于递归,它还是一种组织复杂条件类型的好方法

基本工具1 (Basic tools 1)

Where were we? We said that we needed tools in order to track arguments. It means that we need to know what parameter types we can take, which ones have been consumed and which ones are the next to come. Let’s get started:

我们刚刚说到哪了? 我们说我们需要工具来跟踪论据 。 这意味着我们需要知道可以采用的参数类型,已消耗的参数类型以及接下来要使用的参数类型。 让我们开始吧:

长度 (Length)

To do the analysis mentioned above, we will need to iterate over tuples. Asof TypeScript 3.4.x, there is no such iteration protocol that could allow us to iterate freely (like a for). Mapped types can map from a type to another, but they are too limiting for what we want to do. So, ideally, we would like to be able to manipulate some sort of counter:

为了进行上述分析,我们将需要遍历元组。 从TypeScript 3.4.x开始,没有这样的迭代协议可以让我们自由地进行迭代(如for )。 映射的类型可以从一个类型映射到另一个类型,但是对于我们要执行的操作来说,它们太局限了。 因此,理想情况下,我们希望能够操纵某种计数器

Let’s test it:

让我们测试一下:

By topping a tuple up with any, we created something that could be similar to a variable that can be incremented. However, Length is just about giving the size of a tuple, so it also works with any other kind of tuple:

通过用any 填充一个元组,我们创建了一个类似于可以递增的变量的东西。 但是, Length只是给出一个元组的大小,因此它也可以与任何其他类型的元组一起使用:

前置 (Prepend)

It adds a type E at the top of a tuple T by using our first TS trick:

通过使用我们的第一个TS技巧,它在元组T顶部添加类型E

Let’s test it:

让我们测试一下:

In Length’s examples, we manually increased a counter. So Prepend is the ideal candidate to be the base of a counter. Let’s see how it would work:

Length的示例中,我们手动增加了一个计数器。 因此Prepend是成为计数器基础的理想人选。 让我们看看它如何工作:

下降 (Drop)

It takes a tuple T and drops the first N entries. To do so we are going to use the same techniques we used in Last and our brand new counter type:

它取一个元组T并删除前N个条目。 为此,我们将使用与Last和全新计数器类型相同的技术:

Let’s test it:

让我们测试一下:

What happened?

发生了什么?

The Drop type will recurse until Length<;I> matches the value of N that we passed. In other words, the type of index 0 is chosen by the conditional accessor until that condition is met. And we used Prepend&lt;any, I> so that we can increase a counter like we would do in a loop. Thus, Length<I> is used as a recursion counter, and it is a way to freely iterate with TS.

Drop类型将递归直到Length< ; I>米 atches瓦尔u的N个E,我们通过。 换言之,类型i ndex 0由条件存取选择,直到满足条件。 我们used Prepend&l吨;任何,我>这样我们就可以增加一个计数器一样,我们会在做loop. Thu loop. Thu ,将Length <I> 用作递归计数器,这是一种自由迭代TS的方法。

咖喱V3 (Curry V3)

It’s been a long and tough road to get here, well done! There’s a reward for you ?.

到达这里,路途艰难,艰难! 有奖励给你吗?

Now, let’s say that we tracked that 2 parameters were consumed by our curry:

现在,假设我们跟踪了咖喱消耗了两个参数:

Because we know the amount of consumed parameters, we can guess the ones that are still left to be consumed. Thanks to the help of Drop, we can do this:

因为我们知道消耗的参数数量,所以我们可以猜测仍然需要消耗的参数。 感谢Drop的帮助,我们可以做到这一点:

It looks like Length and Drop are precious tools. So let’s revamp our previous version of curry, the one that had a broken Tail:

看起来LengthDrop是宝贵的工具。 因此,让我们修改一下先前版本的咖喱,咖喱的Tail坏了:

What did we do here?

我们在这里做了什么?

First, Drop<Length<T>, P> means that we remove consumed parameters out.Then, if the length of Drop&lt;Length<T>, P> is not equal to 0, our curry type has to continue recursing with the dropped parameters until… Finally, when all of the parameters were consumed, the Length of the dropped parameters is equal to 0, and the return type is R.

首先, Drop<Length< T>,P>表示我们删除消耗的参数,然后,如果e length of Drop&l t; Length <T> , P>的长度不等于0 ,则我们的咖喱类型h继续递归与下降paramete RS直到......最后,当所有 parame ters w ERE消耗的长度下降 参数等于0, 返回类型为R。

咖喱V4 (Curry V4)

But we’ve got another error above: TS complains that our Drop is not of type any[]. Sometimes, TS will complain that a type is not the one you expected, but you know it is! So let’s add another tool to the collection:

但是上面有另一个错误:TS抱怨我们的Drop不是type any[]类型。 有时,TS会抱怨一种类型不是您期望的那种,但您知道它是! 因此,我们向集合添加另一个工具:

(Cast)

It requires TS to re-check a type X against a type Y, and type Y will only be enforced if it fails. This way, we’re able to stop TS’s complaints:

它要求TS根据类型Y 重新检查类型X ,并且类型Y仅在失败时才被强制执行。 这样,我们就可以停止TS的投诉:

Let’s test it:

让我们测试一下:

And this is our previous curry, but without any complaint this time:

这是我们以前的咖喱,但是这次没有任何抱怨:

Remember earlier, when we lost the type checks because we started tracking consumed parameters with T extends any[]? Well it has been fixed by casting T to Partial<;P>. We added a constraint withCast<T,Partial<P>>!

还记得以前,当我们因为开始使用T extends any[]跟踪消耗的参数而丢失类型检查时, T extends any[]吗? 好吧,已通过将T强制转换为Partial< ; P>进行了修复。 我们用t withCast<T,Pa Partial <P >>添加了约束t withCast<T,Pa

Let’s test it:

让我们测试一下:

咖喱V5 (Curry V5)

Maybe you thought that we were able to take rest parameters. Well, I am very sorry to inform you that we are not there yet. This is the reason why:

也许您以为我们可以接受其他参数。 好吧,很抱歉通知您我们还没有到。 这就是为什么:

Because rest parameters can be unlimited, TS’s best guess is that the length of our tuple is a number, it’s kind of clever! So, we cannot make use of Length while dealing with rest parameters. Don’t be sad, it’s not so bad:

因为rest参数可以是无限的 ,所以TS最好的猜测是我们的元组的长度是一个number ,这很聪明! 因此,在处理其余参数时,我们不能使用Length 。 别难过,还不错:

When all the non-rest parameters are consumed, Drop<Length<;T>,P> can only match […any[]]. Thanks to this, we used [any,…any[] as a condition to end the recursion.

当所有非休息参数都被消耗时, Drop<Length< ; T>,P> only ma匹配[…any []]。 由于日is, we used [任何,任何... []为AC OND银行足球比赛结束递归。

Let’s test it:

让我们测试一下:

Everything works like a charm ?. You just got yourself a smart, generic, variadic curry type. You will be able play with it very soon… But before you do so, what if I told you that our type can get even more awesome?

一切都像魅力吗? 您只是得到了一种聪明, 充满活力, 杂色的咖喱类型。 您很快就能使用它了……但是在您这样做之前,如果我告诉您我们的类型可以变得更出色,该怎么办?

占位符 (Placeholders)

How awesome? We are going give our type the ability to understand partial application of any combination of arguments, on any position. According to Ramda’s documentation, we can do so by using a placeholder called _. It states that for any curried function f, these calls are equivalent:

太棒了 我们将使我们的类型能够理解在任意位置上参数任意组合的部分应用。 根据Ramda的文档,我们可以使用名为_占位符来实现。 它指出,对于任何咖喱函数f ,这些调用是等效的:

A placeholder or “gap” is an object that abstracts the fact that we are notcapable or willing to provide an argument at a certain moment. Let’s start bydefining what a placeholder is. We can directly grab the one from Ramda:

占位符或“空白”是抽象化以下事实的对象:我们没有能力或不愿意在某一时刻提供论据。 让我们先定义一个占位符。 我们可以直接从Ramda那里抢一个:

Earlier, we have learnt how to do our first type iterations by increasing a tuple’s length. In fact, it is a bit confusing to use Length and Prepend on our counter type. And to make it clearer, we will refer to that counter as an iterator from now on. Here’s some new aliases just for this purpose:

之前,我们学习了如何通过增加元组的长度来进行第一类迭代。 实际上,在我们的计数器类型上使用LengthPrepend有点令人困惑。 为了更清楚一点 ,我们将从现在开始将该计数器称为迭代器 。 这是一些新的别名,专门用于此目的:

位置(位置) (Pos (Position))

Use it to query the position of an iterator:

使用它查询迭代器的位置:

下一个(+1) (Next (+1))

It brings the position of an iterator up:

它提高了迭代器的位置:

上一页(-1) (Prev (-1))

It brings the position of an iterator down:

它降低了迭代器的位置:

Let’s test them:

让我们测试一下:

迭代器 (Iterator)

It creates an iterator (our counter type) at a position defined by Index and is able to start off from another iterator’s position by using From:

它在Index定义的位置创建一个迭代器(我们的计数器类型),并能够使用From从另一个迭代器的位置开始:

Let’s test it:

让我们测试一下:

基本工具2 (Basic tools 2)

Good, so what do we do next? We need to analyze whenever a placeholder is passed as an argument. From there, we will be able to tell if a parameter has been “skipped” or “postponed”. Here’s some more tools for this purpose:

好,接下来我们该怎么做? 每当占位符作为参数传递时,我们都需要进行分析 。 从那里,我们将能够判断参数是否已“跳过”或“推迟”。 以下是一些用于此目的的工具:

逆转 (Reverse)

Believe it or not, we still lack a few basic tools. Reverse is going to give us the freedom that we need. It takes a tuple T and turns it the other way around into a tuple R, thanks to our brand new iteration types:

信不信由你,我们仍然缺少一些基本工具。 Reverse将给我们我们所需要的自由。 由于我们全新的迭代类型,它需要一个元组T并将其反过来变成一个元组R

Let’s test it:

让我们测试一下:

康卡特 (Concat)

And from Reverse, Concat was born. It simply takes a tuple T1 and merges it with another tuple T2. It’s kind of what we did in test59:

Concat )从Reverse出生。 它只需要一个元组T1并将其与另一个元组T2合并。 这是我们在test59所做的test59

Let’s test it:

让我们测试一下:

附加 (Append)

Enabled by Concat, Append can add a type E at the end of a tuple T:

Concat启用, Append可以在元组T的末尾添加E类型:

Let’s test it:

让我们测试一下:

缺口分析 (Gap analysis)

We now have enough tools to perform complex type checks. But it’s been a while since we discussed this “gap” feature, how does it work again? When a gap is specified as an argument, its matching parameter is carried over to the next step (to be taken). So let’s define types that understand gaps:

现在,我们有足够的工具来执行复杂的类型检查 。 但是自从我们讨论此“差距”功能以来已经有一段时间了,它又如何工作? 如果将间隙指定为自变量,则其匹配参数将继续进行下一步(要执行)。 因此,让我们定义理解差距的类型:

差距 (GapOf)

It checks for a placeholder in a tuple T1 at the position described by an iterator I. If it is found, the matching type is collected at the same position in T2 and carried over (saved) for the next step through TN:

它在迭代器I所描述的位置检查元组T1中的占位符。 如果找到,则在T2的相同位置收集匹配类型,并通过TN保留(保存)用于下一步:

Let’s test it:

让我们测试一下:

差距 (GapsOf)

Don’t be impressed by this one. It calls Gap over T1 & T2 and stores the results in TN. And when it’s done, it concats the results from TN to the parameter types that are left to be taken (for the next function call):

不要对此印象深刻。 它在T1T2调用Gap ,并将结果存储在TN 。 而当它完成,它concats结果从TN到留采取(下一个函数调用)的参数类型:

Let’s test it:

让我们测试一下:

缝隙 (Gaps)

This last piece of the puzzle is to be applied to the tracked parameters T. We will make use of mapped types to explain that is is possible replace any argument with a placeholder:

难题的最后一部分将应用于跟踪的参数T 我们将使用映射类型来解释是否有可能用占位符替换任何参数:

A mapped type allows one to iterate and alter properties of another type. In this case, we altered T so that each entry can be of the placeholder type. And thanks to ?, we explained that each entry of T is optional. It means that we no longer have the need to use Partial on the tracked parameters.

映射类型允许一个对象迭代和更改另一种类型的属性 。 在这种情况下,我们更改了T以便每个条目都可以是占位符类型。 并感谢? ,我们解释说T每个条目都是可选的。 这意味着我们不再需要对跟踪的参数使用Partial

Let’s test it:

让我们测试一下:

Ugh, we never said that we could take undefined! We just wanted to be able to omit a part of T. It is a side effect of using the ? operator. But it is not that bad, we can fix this by re-mapping with NonNullable:

gh,我们从未说过我们可以采取undefined ! 我们只是希望能够省略T的一部分。 使用? 副作用 操作员。 但这还不错,我们可以通过使用NonNullable重新映射来解决此NonNullable

So let’s put the two together and get what we wanted:

因此,让我们将两者放在一起,得到我们想要的东西:

Let’s test it:

让我们测试一下:

咖喱V6 (Curry V6)

We’ve built the last tools we will ever need for our curry type. It is now time to put the last pieces together. Just to remind you, Gaps is our new replacement for Partial, and GapsOf will replace our previous Drop:

我们已经建立了咖喱类型所需的最后一种工具。 现在是时候将最后的部分放在一起了。 提醒您, GapsPartial的新替代品, GapsOf将替代我们之前的Drop

Let’s test it:

让我们测试一下:

In order to make sure that everything works as intended, I am going to force the values that are to be taken by the curried example function:

为了确保一切正常,我将强制使用咖喱的示例函数采用的值:

There is just a little problem: it seems like we’re a bit ahead of Ramda! Our type can understand very complex placeholder usages. In other words, Ramda’s placeholders just don’t work when they’re combined with rest parameters ?:

只是有一个小问题:似乎我们比Ramda领先! 我们的类型可以理解非常复杂的占位符用法。 换句话说,当Ramda的占位符与其余参数组合在一起时,它们的占位符不起作用 ?:

However, even if this looks perfectly correct, it will result in a complete crash. This happens because the implementation of Ramda’s curry does not deal well with combinations of placeholders and rest parameters. This is why I opened a ticket with Ramda on Github, in the hope that the types we’ve just created could one day work in harmony with the library.

但是,即使看起来完全正确,也将导致完全崩溃。 发生这种情况是因为Ramda的curry的实现不能很好地处理占位符和rest参数的组合。 这就是为什么我在Github上与Ramda一起开票的原因,希望我们刚刚创建的类型有一天可以与图书馆和谐地工作。

咖喱 (Curry)

This is very cute, but we have one last problem to solve: parameter hints. I don’t know about you, but I use parameter hints a lot. It is very useful to know the names of the parameters that you’re dealing with. The version above does not allow for these kind of hints. Here is the fix:

这很可爱,但是我们要解决的最后一个问题是: 参数提示 。 我不了解您,但是我经常使用参数提示。 知道您要处理的参数的名称非常有用。 上面的版本不允许出现此类提示。 解决方法是:

I admit, it’s completely awful! However, we got hints for Visual Studio Code.What did we do here? We just replaced the parameter types P & R that used to stand for parameter types and return type, respectively. And instead, we used the function type F from which we extracted the equivalent of P with Parameters<;F>; and R with ReturnType<F>. Thus, TypeScript is able to conserve the name of the parameters, even after currying:

我承认,这简直太糟糕了! 但是,我们得到了有关Visual Studio Code的提示。我们在这里做了什么? 我们只是替换了分别代表参数类型和返回类型的参数类型PR 取而代之的是,我们使用函数类型 F ,从中提取与Parameters< ; F> ;等效的P 和R with ReturnT ype <F>。 因此,TypeScript能够保留参数名称,即使是在经过以下操作之后也是如此:

There’s just one thing: when using gaps, we’ll lose the name of a parameter.

只有一件事:使用间隙时,我们将丢失参数的名称。

A word for IntelliJ users only: You won’t be able to benefit from proper hints. I recommend that you switch to Visual Studio Code as soon as possible. And it is community-driven, free, much (much) faster, and supports key bindings for IntelliJ users. :)

仅适用于IntelliJ用户:您将无法从适当的提示中受益。 我建议您尽快切换到Visual Studio Code。 它是社区驱动的,免费的,快得多(很多),并且支持IntelliJ用户的按键绑定。 :)

最后的话 (LAST WORDS)

Finally, I would like to inform you that there is a current proposal for variadic types. What you’ve learned here is not going to become obsolete — this proposal aims to ease the most common tuple type manipulations, so it is a very good thing for us. In a close future, it will enable easier tuple concatenations like the Append, Concat, and Prepend we’ve built, as well as destructuring and a better way to describe variable function parameters.

最后,我想通知您,目前针对可变参数类型的建议。 您在这里学到的内容不会过时-此建议旨在简化最常见的元组类型的操作,因此对我们来说是一件非常好的事。 在不久的将来,它将使我们构建的AppendConcatPrepend类的元组连接更加容易,并进行结构Prepend和更好的描述可变函数参数的方法。

That’s it. I know that it’s a lot to digest at once, so that’s why I released a developer version of this article. You can clone it, test it, and change it with TypeScript 3.3.x and above. Keep it close to you and learn from it until you become more comfortable with the different techniques ?.

而已。 我知道一次要消化很多东西,所以这就是为什么我发布了本文的开发人员版本 。 您可以克隆,测试它,并使用TypeScript 3.3.x及更高版本对其进行更改。 让它靠近您并从中学习,直到您对各种技术变得更熟悉为止。

High-five ? if you enjoyed this guide, and stay tuned for my next article!

举手击掌 ? 如果您喜欢本指南,请继续关注我的下一篇文章!

EDIT: It’s available for Ramda 0.26.1

编辑: 它可用于Ramda 0.26.1

Thanks for reading. And if you have any questions or remarks, you are morethan welcome to leave a comment.

感谢您的阅读 。 如果您有任何疑问或评论,欢迎您发表评论。

翻译自: https://www.freecodecamp.org/news/typescript-curry-ramda-types-f747e99744ab/

typescript

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值